Instagram Javascript或香肠飞行路线上的3D游戏



在第一篇关于在Instagram上用面具编程游戏的文章之后,一位客户要求我在Instagram上为他的比萨店创建游戏。该游戏原计划用于商业推广目的。当然,我了解到,从第一篇文章的观点来看,Instagram主题对于Habr社区并不是特别有趣。显然,这项服务仍被认为是金发女郎的一种轻浮娱乐,有关它的文章仅适合作为周五晚间鸡尾酒的阅读。但是,今天,仅星期五。喝一杯鸡尾酒。而且不要忘了邀请金发女郎。但是,未来的技术可能会达到这样的高度,我们将开始在GTA-5中在Instagram上播放。或10。

本文将一次讨论两个游戏。我定做了一个,然后自己定了第二个。在每一款新游戏中,我都面临着新任务,对解决方案的寻找使我熟悉了开发的新细微差别。也许这个故事对增强现实元素的其他开发人员很有用。游戏使用纯Javascript编写,无需使用任何其他库。要显示3D图形,请使用内置的Instagram工具。

游戏1.披萨


那么,第一款游戏的本质是什么。旋转披萨。组件从其表面飞走-香肠,西红柿等碎片。两个玩家必须抓住他们的嘴。正如您可能猜到的,获胜者将是能够抓住更多机会的人。随着时间的流逝,转速增加。对我来说,这个游戏中最有趣的事情是对这些可食用元素的飞行路径进行编程。想象一下编写一个香肠飞行程序。不,当然,我从来没有这么有趣的编程。即使是现在,我仍在笑,写这篇文章。

当客户看到工作原型时,他向我发送了此消息:“ :))”。测试最终结果时出现问题。这包括以下事实:对象无法玩游戏:他们只是笑声爆棚-太有趣了。

让我提醒您,面具和游戏的开发是在Spark AR Studio中进行的。然后从那里您可以将自己的作品上传到Instagram,供所有人查看。值得注意的是,Web技术为开发人员模糊了操作系统之间的界限。您编写了一个代码,该代码随后可以在适用于iOS和Android的Instagram应用程序中运行,而无需进行任何修改或更改。当然,为此付出的代价从根本上降低了脚本速度。

游戏的时间表由客户提供。 3D模型存在一些困难。尤其是,当使用内置动画引擎Spark AR Studio时,事实证明,如果缩放了移动对象,则在游戏中会错误地确定其坐标。但是,如果您没有完全缩放整个对象,而是分别缩放每个对象的网格,则不会观察到这种烦人的效果。我必须离开披萨1:1的比例,并为构成披萨的每个元素规定一定的系数。 FBX格式也存在问题,您需要将FBX格式导出为Instagram游戏的模型。图形设计师发送了带有内置纹理的模型,而且这些纹理是沿着相对路径放置的。 3D编辑器看到了它们,但Spark AR没有。我必须重新打包模型,以使纹理文件与模型分开放置,并沿着一条路径放置。

我遇到的另一个小问题是,负责在屏幕上显示文本的api Saprk AR对象拒绝接受该值作为值,例如游戏得分。很长一段时间我都不明白为什么什么也没显示。我究竟做错了什么?原来,您首先需要将数字转换为字符串(.toString())。对于其他语言,这不用多说,但是对于javascript而言,它是非典型的,而javascript本身总是这样做。

简单的动画


在这个游戏中,对我来说,新事物之一是对3D对象的动画进行编程。 (在我之前的Instagram游戏“井字游戏”中,根本没有动画。)Spark AR Studio中的动画引擎非常具体。它需要一些输入参数,然后将变量与要在其中更改内容的对象进行反应性连接。例如,这将随着时间t更改某些3D对象的y坐标(startValue,endValue-其初始值和最终值):

var driverParameters = {
    durationMilliseconds: t,
    loopCount: Infinity,
    mirror: false
};
var driver = Animation.timeDriver(driverParameters);
var sampler = Animation.samplers.linear(startValue, endValue);
sceneObject.transform.y = Animation.animate(driver, sampler);
driver.start();

为了使比萨食材在太空中飞散,我决定为每个坐标简单地并行运行三个这样的动画。它足以指示初始坐标(startValue)和由比萨饼的旋转角度计算得出的最终坐标(endValue),这样它就可以摆在很远的地方,以防玩家没有用嘴抓住这个“壳”。如果被抓住,则运动停止。我在上一篇文章中已经描述过的张口事件。只有这是两个人的游戏,因此,已经有了两个面孔和两个嘴巴:

FaceTracking.face(0).mouth.openness.monitor().subscribe(function(event) {
    if (event.newValue > 0.2) {
        ...
    };
});

FaceTracking.face(1).mouth.openness.monitor().subscribe(function(event) {
    if (event.newValue > 0.2) {
        ...
    };
});

该游戏的重点是用嘴捕捉飞行成分,换句话说,就是在人的嘴中心周围的特定区域内找到飞行物体。最初,这种计算并不想为我正确完成,但是在引入与嘴坐标绑定的隐藏对象之后,找到了解决该问题的方法,并且该方法行之有效。由于某些原因,嘴巴的直接坐标没有返回。这里的cameraTransform.applyTo是将面部点的坐标简化为3D世界中的坐标的方法(该方法取自文档)。

move: function() {
    //   (   )

    //    
    var object = Scene.root.find('pizzafly');
    var olast = {
        x: object.transform.x.pinLastValue(),
        y: object.transform.y.pinLastValue(),
        z: object.transform.z.pinLastValue()
    };

    //   ,       
    var objectHidden = Scene.root.find('nullObject');
    objectHidden.transform.x = FaceTracking.face(face).cameraTransform.applyTo(FaceTracking.face(face).mouth.center).x;
    objectHidden.transform.y = FaceTracking.face(face).cameraTransform.applyTo(FaceTracking.face(face).mouth.center).y;
    objectHidden.transform.z = FaceTracking.face(face).cameraTransform.applyTo(FaceTracking.face(face).mouth.center).z;

    //   
    var mouth = {
        x: objectHidden.transform.x.pinLastValue(),
        y: objectHidden.transform.y.pinLastValue(),
        z: objectHidden.transform.z.pinLastValue()
    };

    //    
    var d = {
        x: Math.abs(olast.x - mouth.x),
        y: Math.abs(olast.y - mouth.y),
        z: Math.abs(olast.z - mouth.z)
    };

    //  
    if ((d.x > 0.03) || (d.y > 0.03) || (d.z > 0.03)) {
        // 
        ...
    } else {
        // 
        ...
    };

},

随后,我意识到您应该删除深度检查(z坐标),因为在此特定游戏中,视觉上很难估计深度。也就是说,现在可以在飞行中随时张开嘴来捕捉食材。最主要的是x和y的组合。


最后,我在编写此游戏时遇到的最后一个问题是将最终版本的大小限制为4 MB。而且,根据Facebook的推荐,为了使游戏被显示在尽可能多的移动设备上,期望甚至容纳2MB。 3D建模人员是富有创造力的人,他们想要创建具有密集网格和巨大纹理的沉重模型,而完全不关心我们,程序员或游戏的最终性能。我还以某种方式减小了纹理的大小,并以jpg(而不是png)进行了压缩,但是必须将披萨模型本身发送以进行修订(重新拓扑)。结果,尽管如此,仍可以将所有模型,纹理和脚本放入2MB的卷中。游戏继续进行。

一段时间后,她回来说“效果包含太多静态文本”。我必须删除倒计时数字,而不是将它们设置为秒表上的箭头动画,现在秒表开始计时而不是它们。在那之后,游戏被批准了。

游戏2。关于蝴蝶(ButterFlap)



现在,我将讨论创建第二款游戏。我禁不住做游戏,这是我的爱好。在3月8日的前几天,我决定为此假期创建一个Instagram游戏。关于花朵,蝴蝶和糖果的东西。但是我想不出游戏的本质。这个想法在我脑海中旋转。也许蝴蝶会收集糖果并将它们放在篮子里?还是蝴蝶会在空中捡起糖果然后扔掉,而玩家需要用嘴抓住它们?总的来说,我骗了几天,但没有找到解决方案,我意识到,到3月8日,我的游戏仍然没有时间进行审核,因为审核需要3-4天。我已经想离开这家公司,突然间这个想法突然就来了。我不再将游戏与“妇女节”联系在一起,现在它只是一个游戏。

滚动条。风景和各种障碍从左向右移动。玩家必须控制一只可以在屏幕内自由移动的蝴​​蝶,并收集悬挂在空中的钻石。而且,从右到左比图片的其余部分快一点,有云在移动,雨水从中倾泻。您需要躲在花下的雨中。如果蝴蝶掉入水中,则其翅膀会变湿并掉落,这就是游戏的结局。我简单地将游戏命名为:ButterFlap。

是的,这听起来不错。我直接想玩自己。但是我记得我根本不是艺术家。但是,我能够制作3D模型,这比绘制图纸容易。因此,我们决定使用3D滚动条。

平面艺术


我找到了必须改进的在线免版税模型。他将纹理放在没有纹理的纹理上,之前将其放置在纹理地图集中:在没有纹理的模型上可以看到“梯子”形式的伪像,并且可以在游戏中为纹理设置平滑度,这使对象的外观更美观且不那么平坦。那些包含纹理的模型也具有必须修复的缺陷。首先,纹理的大小不是功效的两倍。也就是说,例如,它可以等于1200x1200。我必须压缩到1024x1024。视频处理器可以缩放或在不适当的纹理中填充空白空间,即那些不是1024x1024、512x512、256x256等的纹理。在任何情况下,此类操作都是游戏工作期间的额外负担,并且无用的内存消耗,因此,最好先手动准备正确的图像。我按纹理地图集分发所有纹理的事实也避免了这种情况,因此,例如,如果原始纹理的尺寸为400x200像素,那么我可以将其放在1024x1024地图集中,与其他类似的地图集相邻。此后,自然也有必要对UV扫描进行缩放,但这要在几秒钟内完成。仍然遇到各种模型变体,由于某种原因,纹理沿绝对路径进行捆绑,例如“ C:\ Work \ Vasya \ Map.jpg”。好吧,我的计算机上没有这样的文件夹!我必须手动指定纹理的路径...但是为什么您会想到我应该将所有项目都保存在“ C:”驱动器上!哦,这些建模者,自由艺术家...顺便说一句,通过这种方式,通过随意保留的路径中的文件夹名称,您可以无意中了解有关建模者身份的更多信息,例如他的名字。很高兴见到你!

最困难的事情是找到一个合适的带有动画的蝴蝶模型。 Spark AR使用可以从任何3D编辑器导出为FBX格式的动画。我下载的一对蝴蝶模型的机翼以某种方式奇怪地镜像了-一个机翼被建模,第二个机翼以我不理解的某种方式反射,因此,其中的两个变成了。通过这种建模方法,最终,在游戏中,其中一个机翼(已复制)不想接收来自光源的光线,并且始终保持昏暗状态。我没有冒险大幅度改变模型的风险,因为那样动画就可以飞行了。在动画方面,我比3D建模更大。也许问题出在其他方面:例如,我尝试扩大法线,但这并没有帮助。简而言之,免费的3D模型很痛苦。结果,整个晚上都洗了,通过反复试验,我找到了一个合适的模型,经过整理一些文件后,它看起来令人满意。我不喜欢的东西,但这没什么大不了的:我重新纹理,在某些地方更改了几何形状,并调整了材质参数。最终,蝴蝶起飞了。万岁。但是现在我筋疲力尽。所以我决定上床睡觉,第二天继续。是的,我花了7-8天的时间来制作游戏。但这是晚上的一项相当悠闲的工作,阅读文档,文章并找到问题的答案。所以我决定上床睡觉,第二天继续。是的,我花了7-8天的时间来制作游戏。但这是晚上的一项相当悠闲的工作,阅读文档,文章并找到问题的答案。所以我决定上床睡觉,第二天继续。是的,我花了7-8天的时间来制作游戏。但这是晚上的一项相当悠闲的工作,阅读文档,文章并找到问题的答案。


第二天晚上,我从事图形工作。正如我已经提到的,Instagram游戏面具的特殊性是建议整个游戏的容量不要超过2MB(最大4MB)。我不担心脚本:它的大小不太可能超过50 Kb。但是必须精心设计出超过3D的植物模型。例如,向日葵所在的那部分是通过几何形状制成的。数以百计的多边形...用几十个三角形的球体的一部分替换,并拉伸下载的纹理,实际上是该部分的纹理。叶子的数量也可以减少。我们在场景的底部使用两个三角形的平面(带有带有alpha通道的叠加纹理)制作草。我们在阴影中达到体积,并在纹理本身内部复制图片的片段。

通常,希望最小化纹理的数量及其大小(以字节为单位)。大量的纹理是纹理。我习惯于需要透明度的纹理,放在一个纹理地图集中-png 32位,不使用alpha通道的纹理打包在另一个地图集中,保存为jpg。jpg可以比png缩小。需要透明度的草和UI元素已进入png地图集,其他所有元素均已变为jpg。

总体积。总共,我得到了4种植物,一种是岩石,一种是蝴蝶,由玩家控制,另一种是云。所有这些模型的压缩格式纹理均包含2个地图集1024x1024 jpg和png,总体积为500 Kb。这些模型本身消耗了大约200 Kb。加上脚本。声音-31 Kb。总计:大约1 Mb。绿色仅显示构建的大小(这是2 MB的大小)。


突然我遇到了一个完全意外的问题。我删除了几个未使用的模型,但不是通过Spark AR Studio,而是从文件系统中删除的。随后,在组装构建时,我发现它们从Spark AR Studio场景中消失了,但仍然进入组装中。清除项目中未使用的资源是不可能的。从“资产摘要”到它们的链接不会指向任何地方。显然,这是Spark AR Studio中的缺陷。我不得不重新创建该项目,从一开始就在其中添加了所有必要的资源。

现场


我已经思考了很长时间关于如何实现屏幕上所有对象的滚动。在这些工具中,Spark AR Studio中只有javascript和最简单的内置动画引擎,可以在给定时间内将对象的坐标从初始值更改为最终值。

是的,在此之前仍然出现了难题:是否要通过在需要重复的植物上重复重复的植物并将其放置在正确的位置,从而在游戏中滚动整个场景,还是在3D编辑器中创建整个场景(在3D编辑器中创建场景),还是在每个3D中仅加载一个副本即可物体并朝着屏幕外侧的播放器方向替换它们。答案很明显。我们的选择是第二个选择。否则,绝对无法容纳2 MB:根据第一个选项,场景很可能很重。但是然后您需要对象的布局。我毫不犹豫地决定使用3D编辑器作为关卡编辑器。是的,事实证明我都做到了。为了游戏的植物,我将它们保存在一个副本中。从编辑器中,我只需要坐标。完成工作后,我写出了所有对象的坐标,并用游戏数据组成了一个数组。

lv:[
    {n:'flower1', x:8.0, y:-6.5},
    {n:'cloud', x:10.0, y:0.1},
    {n:'rain', x:10.0, y:6.6},
    {n:'flower1', x:14, y:-2.5},
    {n:'diamond_red', x:20, y:2.0},
	...
],

然而-根据对象的类型,一个单独的关联数组,其中存储了对撞机的大小及其相对于3D对象中心的位移,以及一个标志(col),该标志确定对象是否为障碍物(否则玩家将穿过障碍物)。对于某些类型的对象,设置了(destroy)标志(确定是否在交互后隐藏对象)以及(v)参数,该参数确定了某种程度的交互作用,例如玩家获得或失去的点数。

colld:{
    'flower1': {dx:0, dy:5, w:2.3, h:1.4, col:true},
    'diamond_green': {dx:0, dy:0, w:1, h:1, col:false, v:1, destroy:true},
    'diamond_red':{dx:0, dy:0, w:1, h:1, col:false, v:-2},

    ...
},

有点细微差别。场景底部的草应该连续滚动。因此,您必须使用该对象的两个副本,并在移动时一个接一个地替换它们。花将出现在一个屏幕上,每个屏幕最多出现一个副本。

考虑到我在开发第一个游戏时遇到的比例问题,我在3D编辑器中重置了所有模型的比例。因此,在Saprk中,AR模型会立即以正常大小加载。的确,无论如何,在脚本中,离不开包含宇宙本质的“魔数”,全局系数和通用代码。虚拟世界,当然。我准备向您透露此号码。使用它,人们!我不介意!该数字是0.023423。简而言之,尽管重置了所有比例尺,但3D编辑器中的一米竟然等于Spark AR Studio中的这个数字。尽管如此,我很可能仍无法完全理解使用3D图形的所有复杂性,因此无法完全理解系数。您猜猜是从编辑器导出的所有对象的坐标(而不是大小)乘以它。没有找到在Spark AR中调整场景比例的地方。

我遇到的下一个问题是从3D编辑器导出时对象排序顺序的丢失。由多个网格组成的复杂对象可能会以不可预测的方式出现在游戏场景中,例如,位于另一个网格后面的网格突然向前跳跃。而且,如果在导出到Spark AR Studio之后查看对象的顺序,则实际上表明此网格出于某种原因在列表中较高,尽管在编辑器中较低。我通过将场景划分为多个层并将它们保存到不同的文件来解决了这个问题。

复杂动画


再过三个晚上,我玩弄编程。如果我使用标准的Spark AR Studio引擎为蝴蝶的翅膀设置动画,那么移动背景的任务就不是那么简单。我仍然不明白如何不仅将一个变量参数附加到运动循环的迭代中,而且还附加了完整的回调函数。无论如何,我都做不到,当前的动画参数不想被传递到那里。这个功能是绝对必要的,因为如果在第一场比赛中(带披萨)我通过订阅来张开嘴巴来检查碰撞,那么这里就没有这样的事件。并且仅需根据角色当前的坐标检查角色移动时与环境物体的碰撞。为此,必须在每次迭代时比较坐标。然后我想。毕竟,我已经用JavaScript编写了游戏。为何不使用我之前为这些游戏编写的动画引擎?其操作原理大致相同:在给定的时间内,参数(或多个参数)在给定的初始值和最终值之间变化。而且,当前值作为参数传递给您-您不会相信-在给定的回调函数中,例如,您可以在场景中将3D对象设置为与这些当前值相等的坐标,或者检查它们之间是否存在碰撞。谢谢,章我不得不略微调整引擎以适应本地的“生态系统”:从此处删除对window对象的引用,因为它不在这里,以及其他一些小东西。而且,当前值作为参数传递给您-您不会相信-在给定的回调函数中,例如,您可以在场景中将3D对象设置为与这些当前值相等的坐标,或者检查它们之间是否存在碰撞。谢谢,章我不得不略微调整引擎以适应本地的“生态系统”:从此处删除对window对象的引用,因为它不在这里,以及其他一些小东西。而且,当前值作为参数传递给您-您不会相信-在给定的回调函数中,例如,您可以在场景中将3D对象设置为与这些当前值相等的坐标,或者检查它们之间是否存在碰撞。谢谢,章我不得不略微调整引擎以适应本地的“生态系统”:从此处删除对window对象的引用,因为它不在这里,以及其他一些小东西。

是的,但是-关于滚动环境的景观和对象。我决定将整个世界放在一个NullObject中,即在一个空的3D对象中,在一个容器中,并仅使用一个动画参数-x坐标来移动它。在容器内部,所有模型都具有与外部相同的坐标,只是现在参考系统已将其绑定到该空对象。岩石和花朵将重复出现(此外,根据水平图,它们在离地面的不同高度),因此您可以在移动时重复使用这些对象,并将它们设置为“容器”内所需的水平和垂直位置。我写了一个搜索系统,搜索落入框架中的对象(在当前容器偏移处),该系统将离开框架的对象设置为可以放置在新位置的位置。你看,它如何在三个对象的示例上工作。 (游戏中将有更多这些对象,因此您将不再看到环境对象“重新排列”的这种效果。)



用于更新对象坐标的函数如下所示:

oCoordSet: function(v) {
    //  :    
    //   
    var camx = -ap.oWorld.transform.x.pinLastValue();
    //  
    var x1 = camx - ap.game.scro.w2;
    var x2 = camx + ap.game.scro.w2;
    //   
    for (var i = 0; i < ap.d.lv.length; i++) {
        //    
        if ((ap.d.lv[i].x >= x1) & (ap.d.lv[i].x <= x2)) {
            //   
            ap.d.lv[i].o = Scene.root.find(ap.d.lv[i].n);
            //  -  
            ap.d.lv[i].o.transform.y = ap.d.lv[i].y;
            if ((ap.d.lv[i].n == 'cloud') || (ap.d.lv[i].n == 'rain')) {
                //    ,
                //  
                //   2.3,
                //     
                ap.d.lv[i].o.transform.x = ap.d.lv[i].x - (x2 - ap.d.lv[i].x) * 2.3 + 0.2;
            } else {
                //    ,
                //      
                ap.d.lv[i].o.transform.x = ap.d.lv[i].x;
            };
        };
    };
    //        
    //     
    if (camx > ap.game.grassPosLast) {
        ap.game.grassPosLast += ap.game.grassd;
        ap.game.grassi = 1 - ap.game.grassi;
        ap[ap.game.grassNm[ap.game.grassi]].transform.x = ap.game.grassPosLast;
    };
},

主角


游戏的主要角色是一只不沉的蝴蝶,它大胆向前飞,克服了障碍。我决定通过旋转和倾斜头部来设置标记或光标(亮点)来进行管理。朝着这一点的方向,蝴蝶会缓慢地飞行(实际上,它不是战斗机,也不是可以移动的)。例如,如果您预订了头部倾斜事件,则可以像这样实现垂直控制:

FaceTracking.face(0).cameraTransform.rotationX.monitor().subscribe(function(event) {
    var v = event.newValue;
    //  
    var scrH2 = ap.game.scr.h2;
    //
    var p = -v * 0.5;
    if (p < -scrH2) {
        p = -scrH2;
    } else if (p > scrH2) {
        p = scrH2;
    };
    //  
    var d = 0.006;
    //  
    var cur = ap.oPers.transform.y.pinLastValue();
    if (p < cur) {
        cur -= d;
        if (cur < p) {cur = p;};
    } else {
        cur += d;
        if (cur > p) {cur = p;};
    };
    //    ()
    ap.oPointer1.transform.y = p;
    //   ,
    // ,    
    ap.game.pers.dy + = cur - ap.game.pers.y;
});

水平方向也一样。仅在这里您需要订阅事件,而不是倾斜,而是旋转头部(rotationY),而不是屏幕的高度,请考虑其宽度。

撞机


所有这些都很棒,世界在移动,游戏角色可以在屏幕上自由移动。但是现在您需要一个碰撞处理程序,否则将无法进行任何游戏。游戏中存在三个事件,根据这些事件角色的位置可以改变。这是头部的旋转和倾斜,以及世界的运动,其中玩家(x)的水平坐标会自动增加。

由于我不知道人脸处理程序的迭代如何在Spark AR中工作-不管是以特定频率调用还是将其设置为最大可能的时钟频率,并且在动画引擎中我可以控制此参数,因此我决定在函数中定义碰撞世界的运动,以我设定的频率(每秒60帧)进行调用。在面部处理事件中,我们只会“累积”运动。

原理就是这样。在磁头倾斜和磁头转动事件中,会沿x和y轴累积偏移。此外,在滚动世界的功能中,水平位移也被添加到“存钱罐”。然后检查,如果将累积位移添加到原始坐标,则将与世界上的任何对象发生碰撞。如果不是,则玩家的原始坐标加上偏移量由当前坐标确定。然后我们将源位置更新为当前位置(重置)并重置偏移量。如果是这样,则将坐标回滚到原始坐标。此外,由于两个坐标都无法回滚,因此我们需要确定碰撞将在哪个轴上。否则,玩家只会“坚持”到一个空间点,而不能再移动到任何地方。必须给予他沿着不会发生碰撞的轴的运动自由度。这样您就可以给他一个绕障碍飞行的机会。

setPersPos: function(camx, camdx) {
    //   
    //       (camx,camdx)

    //     
    var persx = ap.game.pers.x;
    var persy = ap.game.pers.y;
    var dx = ap.game.pers.dx;
    var dy = ap.game.pers.dy;

    // ,     
    var col = ap.collisionDetect(
        {x: persx, y: persy, dx: dx, dy: dy},
        {x: camx, dx: camdx, y: 0}
    );

    if (col.f == true) {
        // 

        if (col.colx == true) {
            //   
            //    ,
            //   
            //(    ,    )
            ap.game.pers.x = col.x;
        } else {
            //    ,
            //   
            ap.game.pers.x = persx + dx;
        };

        if (col.coly == true) {
            // -  
            ap.game.pers.y = col.y;
        } else {
            ap.game.pers.y = persy + dy;
        };

    } else {
        //  ,   
        ap.game.pers.x = persx + dx;
        ap.game.pers.y = persy + dy;
    };

    // 
    ap.game.pers.dx = 0;
    ap.game.pers.dy = 0;

    //     
    ap.oPers.transform.x = ap.game.pers.x;
    ap.oPers.transform.y = ap.game.pers.y;
},

碰撞检测功能本身:

collisionDetect: function(opers, ow) {
    // , opers -   , ow -  

    var res = {f: false, colx: false, coly: false, x: 0, y: 0};

    var ocoll, x, y, w, h, persx, persy, persx0, persy0, od, colx1, coly1, colx2, coly2;
    var collw = false, collh = false;

    //  
    //(  ,  "" )
    persx0 = opers.x + ow.x - ow.dx;
    persy0 = opers.y + ow.y;

    //       
    persx = persx0 + opers.dx;
    persy = persy0 + opers.dy;

    //  
    for (var i = 0; i < ap.d.lv.length; i++) {
        od = ap.d.lv[i]; //obj data

        //    (   ),
        //     
        //        
        if (typeof ap.d.colld[od.n] !== "undefined") {

            //       
            ocoll = ap.d.colld[od.n];
            colx1 = od.x + ocoll.x1;
            colx2 = od.x + ocoll.x2;
            coly1 = od.y + ocoll.y1;
            coly2 = od.y + ocoll.y2;

            if ((persx < colx1) || (persx > colx2) || (persy < coly1) || (persy > coly2)) {} else {
                //   
                res.f = true;

                //        ,
                //,    
                if ((persx0 < colx1) || (persx0 > colx2)) {
                    collw = true;
                };
                //        ,
                //,    
                if ((persy0 < coly1) || (persy0 > coly2)) {
                    collh = true;
                };

            };
        };

    };

    //   

    //  ,     ,
    //  
    if (collw == true) {
        res.colx = true;
        res.x = persx0 - ow.x;
    } else {
        res.x = opers.x;
    };

    // -  
    if (collh == true) {
        res.coly = true;
        res.y = persy0 + ow.y;
    } else {
        res.y = opers.y;
    };

    return res;
},


我使用标准的粒子引擎Spark AR Studio来动画雨。这里没有什么特别的。我们向场景添加一个Emtter对象,不要忘记询问它Emitter-> Space-> Local(而不是World)。这是为了确保水滴在移动过程中不会动态地滞后于云层,而是始终直接掉落。此方法更方便,可以更轻松地确定蝴蝶在雨中掉落的时间-无需校正高度。对于滴剂本身,我准备了合适的质地。好吧,当然,雨对象将与云对象一起移动。然后,我在碰撞处理代码中添加了云击条件。而且,如果此时玩家上方没有障碍物,则蝴蝶掉落,游戏结束。

适度


为了成功进行审核,游戏中的强制性视频不应包含与对象无关的静态文本。否则,机器人会立即停止审核。但是,此后,一个人会检查游戏几天。而且他不喜欢比赛前和比赛结束时的大量文字。我不得不减少文字量。在那之后,游戏被批准了。

摘要


并不是说我的游戏特别激动。她可能缺乏动力和其他技巧。我将发布更新。但是我意识到在Instagram上制作游戏很有趣。这是一种挑战。在工具和分配的内存数量方面,我非常享受以极简主义进行编程和解决各种任务的过程。我记得有人说640 Kb足够每个人使用。现在尝试安装2 MB的3D游戏。我也许不会说他们对每个人都足够……但是请尝试!


最后,我想将我遇到的所有不明显的时刻汇总在一起。在为Instagram创建游戏时,这可能对作弊者有用。

  • 规模。在3D编辑器中调整所有3D模型的比例,以使他在游戏舞台上立即以1:1的比例。
  • . . , .
  • . FBX, . -. , .
  • . JPG , , , -, JPG, PNG.
  • . , , . Spark AR , . , , , 3D-.
  • 3D , : , . . .
  • , , . javascript .
  • 在Spark AR的上下文中,没有窗口对象,并且没有各种特定于浏览器的对象,例如webkitRequestAnimationFrame,mozRequestAnimationFrame等。用JavaScript动画编程时,这是一种命运。

All Articles