这是我第二次参加JS13KGAMES挑战赛,这是一个为期一个月的竞赛,比赛于8月13日至9月13日进行,涉及创建一个压缩后可容纳13 KB内存的HTML5游戏(请考虑将游戏果酱与代码高尔夫混在一起)。
今年,我制作了《巴黎的游客》,这款游戏讲述了在一个陌生城市迷路的焦虑感(官方JS13KGAMES条目位于http://js13kgames.com/entries/a-tourist-in-paris或我的网站http: //herebefrogs.com/a-tourist-in-paris/)。
- Простотаданныхзалогпроизводительности
- “做疯狂的事情,或死于尝试”-通往世界漂泊的道路。
- 如何使用PureScript Native和C ++创建3D游戏
- 7个学习Unity 3D的资源
- Block Breaker-我在Unity中的第一个游戏

不要忘了也查看Blade Gunner的事后验尸,这是我去年参加挑战赛的参赛作品。
游戏构想
今年的想法并不容易。 主题是“迷失”。 我在包含该词的表达式列表中搜索灵感,这似乎可以归结为两点:要么需要找到放错地方的东西,要么您是迷失的人,需要找回自己的路(后者可能只是一个前者的特殊情况)。 对于一个顿悟如此重要……集思广益,我想起了宫本茂(Shigeru Miyamoto),他说他对《塞尔达传说》的灵感来自他对童年时代的日本Sonobe周围山坡的探索。 我的童年探索之旅是在法国巴黎的街道上进行的,因此我想到了在光明之城迷路的一位游客的苦难,他们拼命地寻找他想参观的古迹,然后再乘公共汽车前往下一个欧洲首都。
该游客对他周围的街道的视野非常有限(类似于现实生活中的视线),没有地图,而是依靠视觉记忆来导航随机生成的街道迷宫。 倒计时将指示游览巴士出发之前的剩余时间,表示比赛结束。
艺术方向
我梦见了Tribute Games艺术家Johan Vinet令人惊奇的Pico8 pixelart风格,启发了像素化的Haussmannian公寓楼,咖啡厅和餐厅店面,以及微小的服务员和旁观者:

这个有趣的前提绝对是太重艺术了,在放松我的《 Pixel Dailies》练习后,我很快就撞墙了。 不可能从我的脑海中得到一个像样的游戏模型,或者为我的小游客想出一种风格(在拥堵时生产较小的资产更快)。
我差点转向一个不同的想法:一个搜索与救援游戏,玩家驾驶直升机领取滞留在岛屿上的沉船幸存者。 货运量和燃油消耗将影响直升机的射程和玩家安全返回本国基地的能力,给游戏玩法带来一些压力,但允许玩家做出决定来影响游戏结果。 该地图可能已按程序生成。 但是我最终放弃了这个概念,因为飞机可以直线飞行越过障碍物,因此地图本来只能降为背景,而不是游戏的活跃组成部分。
当一个明智的朋友说服我在我的创造性干旱中发力,并首先使用简单的占位符而不是pixelart来解决《巴黎旅游者》的游戏实现时,我开始感到绝望。 这与我的信念背道而驰(请参阅我在上次验尸中汲取的第二课,或如何使它有趣以使您创建游戏),但对帮助我及时完成游戏并为巴黎观光客提供抽象风格起了决定性作用。
技术选择
对于巴黎地图,我想尝试过程级别生成。 我的本能是设计一种算法,将像素化版本的LEGO City板块组装在一起,就像我小时候做很多不同的砖砌城镇一样。

事实证明,这并不像似乎以一种明智的方式将所有这些街道连接在一起,同时又避免了地图中无法到达的“岛屿”那样简单:

- 我使用了pixelart字体(由Peters和Flo提供-他们在JS13KGAMES 2016游戏Glitch Hunter中创建了它)。 1像素的黑色轮廓线可帮助突出文本,而不管其背景颜色如何,这很有用,因为我的设计无法预测。
- 分数是玩家在游览车离开前已经可以参观的古迹数量,并且游戏结束时玩家可以在推特上发送该数字(非常感谢Ryan Malm允许我使用他的JS13KGAMES 2016中的Twitter代码游戏超级故障盒)

- 我实现了移动控件(它们远非完美,但这是第一步)。 除了让我进入拥挤程度较小的“手机”类别(53个提交类别,而“桌面”类别为222个类别)之外,我还可以在智能手机或平板电脑上玩“巴黎的旅行者”:上下班时在移动设备上使用新的独立游戏,当我无法通过标题屏幕时,我总是感到沮丧,因为它们仅支持键盘和鼠标。 到家(或工作地点)后,我几乎没有时间再去玩这些游戏,这对于游戏开发者和潜在玩家来说都是一个浪费的机会。
得到教训
#1-在卡纸开始之前准备好样板代码
与JS13KGAMES 2016(这是我的第一场比赛)不同,我很幸运没有从零开始。 我在JS13KGAMES 2017之前参加了另外2个游戏果酱,并以自上而下的风格进行了迭代(一个有意识的决定,在我解决诸如碰撞响应之类的其他主题之前,不必处理模拟重力),并逐步推动我的游戏框架发展。
但是每次卡纸,我都花了一些时间来尝试最好的入门方法:将以前游戏中的通用样板内容(游戏循环,输入处理,实用程序和构建脚本)复制到一个全新的项目中是否容易? ,还是只保留以前的游戏框架的代码?
无论哪种方式,在卡纸开始之前都回答这个问题是值得的。 时间是有限的,因此最好花时间在创建独特功能上,而不是花费大量精力来使您的游戏脱颖而出。
#2-在遇到麻烦之前要有一些游戏创意。
我当然想出了一个有趣的前提,但努力寻找独特的游戏机制或平衡仅靠运气的游戏玩法。
代号高尔夫球手MaximeEuzière(也称为xem)在他的Lossst验尸报告中分享了有关如何解决此问题的宝贵建议:
自从上次发布JS13k以来,我已经在纸上列出了十几个游戏概念(主要是益智或反思游戏,因为这是我最喜欢的游戏类型),并等待了今年的主题,以查看哪种想法最适合它。
也许主题会激发您的灵感。 也许您不需要这样的想法列表。 但是,有了一个备份计划,口袋里有几个选择,也没有什么坏处。
#3 —加入JS13KGAMES Slack频道
js13kgames.slack.com是了解其他JS13KGAMES参与者,互相反弹游戏想法,分享您的进步,寻求有关HTML / CSS / JS问题的帮助或帮助他人解决问题,寻求反馈的好方法您的游戏演示或测试其他人的演示,与Andrzej Mazur(在2012年创建JS13KGAMES的超级英雄,至今组织了所有版本)的超级朋友结识并聊天……总而言之,成为您刚刚加入的惊人在线社区的一部分通过参加JS13KGAMES竞赛。 在slack.js13kgames.com上获得您的邀请。
#4-了解您的代码混淆器
诸如UglifyJS或Google Closure Compiler之类的代码混淆器是压缩游戏的简便方法,但是编写代码的方式会影响混淆器的效率。
全局变量:默认情况下,UglifyJS将积极地缩短局部变量名称。 这样做是安全的,因为无法在其功能范围之外访问它们。 但是,这将使全局变量和函数名保持不变,因为它们可以在UglifyJS未处理的其他JavaScript文件中进行引用(如果您正在发布类似jQuery的库,情况就是如此)。
// src / game.js
var atlas = {hero:new Image('hero.png')};
函数render(){
var posX = 0,
posY = 0;
drawImage(atlas.hero,posX,posY);
}
render();
〜$ uglifyjs src / game.js -o dist / game.js --mangle
// dist / game.js(为清楚起见,缩进)
var atlas = {hero:new Image('hero.png')};
函数render(){
var a = 0,//仅重命名局部变量
b = 0;
drawImage(atlas.hero,a,b);
}
render();
对于游戏,这是节省一些额外字节的机会。 通过将整个代码包装到IIFE(立即调用的函数表达式)中,全局变量将变成IIFE范围局部的变量,并且代码混淆器可以发挥作用。
// src / game.js
(function(){// IIFE
var atlas = {hero:new Image('hero.png')};
函数render(){
var posX = 0,
posY = 0;
drawImage(atlas.hero,posX,posY);
}
render();
})()
〜$ uglifyjs src / game.js -o dist / game.js --mangle
// dist / game.js(为清楚起见,缩进)
(function(){
var a = {hero:new Image('hero.png')};
函数b(){// IIFE局部变量被重命名
var c = 0,
d = 0;
drawImage(a.hero,c,d);
}
b();
})()
IIFE确实使您花费了几个额外的字节,但是具有将代码隐藏在函数范围内的好处,从而可以防止全局变量被外部脚本覆盖。
如果您不担心全局冲突,可以通过将--toplevel
标志传递给UglifyJS来--toplevel
,该标志指示其将全局变量视为本地变量。
// src / game.js
var atlas = {hero:new Image('hero.png')};
函数render(){
var posX = 0,
posY = 0;
drawImage(atlas.hero,posX,posY);
}
render();
〜$ uglifyjs src / game.js -o dist / game.js --mangle --toplevel
// dist / game.js(为清楚起见,缩进)
var a = {hero:new Image('hero.png')};
函数b(){//即使全局也已重命名
var c = 0,
d = 0;
drawImage(a.hero,c,d);
}
b();
对象属性与数组索引 :UglifyJS不会重命名对象属性,因为它们可以被访问。 如果仅通过带有显式字符串文字的点符号或方括号提取对象属性,则--mangle-prop
标志可以为您节省更多字节。 但是,如果您通过动态变量(通常是我的情况)访问属性,则此标志将破坏您的代码。
// src / game.js
var atlas = {hero:new Image('hero.png')};
var entityType ='hero';
函数render(){
drawImage(atlas.hero,0,0); //点表示法
drawImage(atlas ['hero'],10,10); //带字符串的方括号
drawImage(atlas [enemyType],20,20); //带var的方括号
}
〜$ uglifyjs src / game.js -o dist / game.js --mangle --mangle-props --toplevel
// dist / game.js(为清楚起见,缩进)
var a = {a:new Image('hero.png')};
var b ='hero'; //值未重命名为“ a”
函数c(){
drawImage(aa,0,0); // 好
drawImage(a ['a'],10,10); // 好
drawImage(a [b],20,20); // KABOOM! 英雄不再存在
}
一种解决方法是使用数组代替对象,并按索引而不是属性访问值。
// src / game.js
var indexHero = 0,
indexEnemy = 1;
var atlas = [new Image('hero.png'),new Image('enemy.png')];
函数render(){
drawImage(atlas [indexHero],0,0);
}
〜$ uglifyjs src / game.js -o dist / game.js --mangle --toplevel
// dist / game.js(为清楚起见,缩进)
var a = 0,
b = 1;
var c = [new Image('hero.png'),new Image('enemy.png')];
函数d(){
drawImage(c [a],0,0); //索引像其他变量一样重命名
}
我很高兴在“巴黎的游客”中尝试了这种方法,但是感觉到它既是福也是祸:与使用对象相比,我确实获得了更好的压缩效果,但是代码编写起来较慢,难以读取,并对数组中的值进行重新排序如果我不小心混淆了索引,则容易出错。
结论(或“所以……我做了一个迷宫游戏”)
我对自己的创作有些矛盾。 一方面,有趣的前提不能替代有趣的游戏机制或平衡的游戏玩法。 被剥夺了像素艺术资产的“巴黎旅游者”的真实面目是暴露的:一个随机的迷宫游戏,在这里您无法利用任何技能来影响自己的命运。 游戏玩法依赖于足够快地踏上纪念碑的好运。 另一方面,这种无助感,随机探索的不公平性质以及不可避免的公交启程倒计时与主题相得益彰,因为它们抓住了迷路的本质。
即使“巴黎的游客”在比赛中的表现不佳,我也很满意又推出了一款游戏。 这次,我不得不尽早接受像素艺术风格,这迫使我要充分利用程序上的占位符并进行实验,直到他们发展出自己的抽象风格为止。 我在困扰过去游戏开发(尤其是碰撞响应)的技术问题上也取得了重大进展,这将有助于将来解决游戏问题。
如果您一直在寻找借口或动力来开始您的游戏想法,JS13KGAMES是一个绝佳的机会:参与其中很有趣,也是向他人学习的好地方(有数百种技巧)通过浏览过去的条目的代码来发现)。 13kb的限制将迫使初学者想象一场简短的比赛,而不是一场不合理的大型比赛,并挑战经验丰富的游戏开发人员在比赛提交中梦dream以求的所有内容和水平。