您如何将一个模糊的想法变成一个游戏-从技术细节中获取有趣和具有挑战性的内容? 最近,我发现自己想知道CSS过渡是否可以用于制作某种游戏。 本文是关于这个想法的探索,以及它如何发展成为一种优雅的(据我所知)独特的游戏玩法。
基本思想
基本思想是使用播放器部分控制的缓慢过渡来为对象的left
和top
位置设置动画。 因此,我们将需要一个游戏区域-我们将其称为棋盘和一个动画对象-我们将其称为球 :
<body>
<div id="board">
<span id="ball"></span>
</div>
</body>
板的纵横比为3:2,而球的宽度为5%。 这些值都不是特别重要的,它们似乎才是最合适的-选择了宽高比,以便可以(最终)适合iPhone屏幕,并且球相对较小,因此有足够的移动空间。 下图演示了基本布局,其中球位于板的左上角。
球具有负边距,以使其自身的宽度和高度偏移一半,以便我们在球上设置的任何位置都将是球的中心原点(例如,第一个演示中的球位于0,0
)。 这是该演示的CSS:
#board
{
position:relative;
display:block;
width:720px;
height:480px;
margin:24px auto 0 auto;
border-radius:2px;
background:#fff;
box-shadow:0 0 16px -2px rgba(0,0,0, 0.5);
}
#ball
{
position:absolute;
left:0;
top:0;
display:block;
width:36px;
height:36px;
margin:-18px 0 0 -18px;
border-radius:18px;
background:#f00;
box-shadow:inset 0 0 0 2px rgba(0,0,0, 0.35), 4px 10px 10px rgba(0,0,0, 0.15);
}
理想情况下,我们将根据可用的窗口或屏幕空间动态应用木板和球的尺寸(这对于将游戏移植到移动浏览器是必不可少的),但是为了使这些示例简单易行,尺寸是固定的-木板为720× 480,球为36×36。
现在可以用百分比坐标来描述球的可能运动范围-从左上角的0%,0%
到右下角的100%,100%
。 使用百分比比计算像素更简单,并且可以在将来灵活调整尺寸。
现在,我们可以通过应用一些简单的JavaScript轻松地控制位置,该JavaScript根据方向性按键设置来设置left
或top
位置,即,如果按下了Left Arrow,则将style.left
设置为"0"
,或者如果Down Arrow是按下,然后将style.top
设置为"100%"
:
var
ball = document.getElementById('ball'),
positions =
{
37 : ['left', '0'],
38 : ['top', '0'],
39 : ['left', '100%'],
40 : ['top', '100%']
};
document.addEventListener('keydown', function(e, data)
{
if(data = positions[e.keyCode])
{
ball.style[data[0]] = data[1];
e.preventDefault();
}
}, false);
positions
数组为每个箭头keyCode
定义了一个属性和值,并且在第一个条件中还使用了该数组来知道是否完全按下了箭头键,在这种情况下,我们必须使用preventDefault()
来阻止其本地操作(因此该页面无法同时滚动)。 再次为了简单起见,我没有进行任何功能检测来筛选较旧的浏览器。 实际上,我们希望对浏览器进行预测试,以确保完全支持转换。 以下演示允许将球移动到任何角落。
接下来,让我们添加一个缓慢的transition
规则来对运动进行动画处理。 请注意包含供应商前缀。
#ball
{
-moz-transition:all 5s ease;
-ms-transition:all 5s ease;
-o-transition:all 5s ease;
-webkit-transition:all 5s ease;
transition:all 5s ease;
}
现在,箭头键更改不会触发快速移动,而是会触发球在整个板上缓慢缓慢地移动。 而且由于每次按键仅改变left
或 top
位置(两者都不改变),所以总体效果是一种新颖且相当优雅的运动-一种“弹性”,其编写起来会更加复杂:
例如,尝试该演示中的以下操作:
- 刷新页面以重置球
- 然后按一次向右箭头
- 等到球传中(2.5秒后)
- 然后按一次向下箭头
按下向右箭头将开始向右移动球的过渡,然后按下向下箭头将触发第二个将球向下移动的过渡。 但是第二个过渡不会影响第一个过渡,后者仍将继续,并且总体效果是一条平滑的曲线 -描述了从顶部中心到右下角的弧线。
完善游戏玩法
现在,我们可以使用箭头键将球移动到棋盘内部的任何位置,以建议移动方向。 这提供了控制权,但不是完全控制权,这是可玩游戏的基本挑战。 由于过渡的应用方式,我们拥有的控制量也有所不同。 例如,如果当您按向右箭头时球位于"left:0"
,则需要五秒钟才能到达右边缘(如预期)。 但是,如果按向右箭头时球已经在"left:80%"
,则仍然需要整整五秒钟的时间才能向右边缘移动那么小的距离。 换句话说,球的速度取决于当改变方向时,它与指定方向的接近程度。
转换时序功能的选择也有很大的不同。 在这些示例中,我使用了"ease"
功能,该功能等同于以下贝塞尔曲线 :
该图显示了相对速度,并说明了它在开始时如何加速,然后在最终时减速。 因此,球在过渡的起点和终点附近的移动会更加缓慢,这将使其在这些点上的控制变得更加容易。 实际上,通过快速连续地改变其方向,可以使球几乎保持静止。
增加真正的挑战
现在我们有一个很好的可玩动作,但我们仍然没有游戏。 一定有挑战性,实际上是您必须在受限的控制范围内做的事情 。 也许我们可以使用相同的过渡添加额外的内容?
由于我们已经定义了适用于"all"
属性的过渡,因此我们可以简单地扩展JavaScript,以便每个箭头键也可以应用背景色的更改 ,并使用不同的粗体颜色来与每个方向相对应:
var
ball = document.getElementById('ball'),
positions =
{
37 : ['left', '0'],
38 : ['top', '0'],
39 : ['left', '100%'],
40 : ['top', '100%']
},
colors =
{
37 : '255,0,0',
38 : '255,255,0',
39 : '0,0,255',
40 : '0,255,255'
};
document.addEventListener('keydown', function(e, data)
{
if(data = positions[e.keyCode])
{
ball.style[data[0]] = data[1];
ball.style.backgroundColor = 'rgb(' + colors[e.keyCode] + ')';
e.preventDefault();
}
}, false);
现在,通过按箭头键,我们不仅可以更改球的位置,还可以更改其原色。 让我们还将球的默认位置移到中心,并将其默认颜色设置为灰色(即,将其设置为中等明亮的颜色,该颜色在比赛中将永远不会出现):
但是,当然,颜色并不会立即改变,而是会在单个过渡过程中从一种颜色逐渐淡入另一种颜色,并沿途穿过各种中间阴影。 例如,如果球为红色,然后按向右箭头 ,则它将通过各种深浅的紫色(以及向右移动)从红色变为蓝色。
由于每个方向都有不同的颜色,所以相同的运动也可能导致不同的颜色。 例如,如果您按向右箭头,然后快速按向下箭头 ,则球将移动到右下角并逐渐变为青色(因为青色被映射为向下)。 但是,如果以相反的顺序(向下然后向右)按这些键,则球仍将移动到相同的角,但是这次淡化为蓝色(因为蓝色映射到右侧)。 因此,对于任何给定的物理位置,球可能具有多种可能的颜色阴影。
现在我认为,我们拥有制作游戏所需的一切。 如果很难完全控制球,并且很难将其变为特定的颜色,那么我们可以说必须将球移至特定的位置和 特定的颜色,从而给比赛带来挑战。
最终游戏原型
我们将添加一系列具有不同背景颜色的其他元素(我们称它们为目标 ),然后添加脚本来监视球的位置和颜色。 如果球也位于相同颜色的目标区域内,则我们将其称为比赛,目标消失。 这很容易描述,但实际上已被复杂化为脚本,如下所示。
var targets =
[
{ "color" : [220,180,40], "coords" : [5,5,12,35] },
{ "color" : [210,80,80], "coords" : [45,2.5,10,40] },
{ "color" : [160,90,60], "coords" : [65,5,20,20] },
{ "color" : [100,100,150], "coords" : [2.5,75,35,15] },
{ "color" : [150,70,100], "coords" : [55,65,10,20] },
{ "color" : [70,230,150], "coords" : [87.5,60,10,20] }
];
for(var len = targets.length, i = 0; i < len; i ++)
{
var target = document.createElement('div');
target.className = 'target';
target.style.left = targets[i].coords[0] + '%';
target.style.top = targets[i].coords[1] + '%';
target.style.width = targets[i].coords[2] + '%';
target.style.height = targets[i].coords[3] + '%';
target.style.backgroundColor = 'rgb(' + targets[i].color.join(',') + ')';
targets[i].target = ball.parentNode.insertBefore(target, ball);
}
var tracking = window.setInterval(function()
{
var ballcolor = window.getComputedStyle(ball).backgroundColor.replace(/[^0-9,]/g, '').split(',');
for(var n = 0; n < 3; n++)
{
ballcolor[n] = parseInt(ballcolor[n], 10);
}
for(var i = 0; i < targets.length; i ++)
{
if
(
ball.offsetLeft > targets[i].target.offsetLeft
&&
ball.offsetLeft + ball.offsetWidth < targets[i].target.offsetLeft + targets[i].target.offsetWidth
&&
ball.offsetTop > targets[i].target.offsetTop
&&
ball.offsetTop + ball.offsetHeight < targets[i].target.offsetTop + targets[i].target.offsetHeight
)
{
var match = 0;
for(var n = 0; n < 3; n ++)
{
if(Math.abs(ballcolor[n] - targets[i].color[n]) < 40)
{
match ++;
}
}
if(match === 3)
{
targets[i].target.parentNode.removeChild(targets[i].target);
targets.splice(i, 1);
if(targets.length === 0)
{
window.clearInterval(tracking);
window.setTimeout(function(){ alert('Yay!'); }, 250);
}
}
}
}
}, 62.5);
比较颜色时,我们必须留出一定的余地。 我们不能期望球和目标完全相同 (这几乎是不可能的),因此我们要从另一个中减去一个,并考虑到最大差异。 因为我们需要这样做,所以必须使用RGB来应用颜色,因为RGB值更易于编程使用:
var match = 0;
for(var n = 0; n < 3; n ++)
{
if(Math.abs(ballcolor[n] - targets[i].color[n]) < 40)
{
match ++;
}
}
if(match === 3)
{
//... all three channels are sufficiently close
}
跟踪代码本身包装在单个setInterval()
循环中(据我所知)是持续监视球的属性的唯一方法-使用getComputedStyle()
和offset属性,以获取球的颜色和位置每次迭代。 该间隔不应太快,以至于不会给浏览器造成太大的压力,但它仍然必须足够快才能准确 -基于球的大小和速度。 由于球是板的5%,并且在五秒钟内移动了整个距离,因此,球平均需要250ms
才能移动自己的宽度。 因此,无论我们将其用于间隔的任何比例,都将最大跟踪漂移表示为球尺寸的一部分,即,球的间隔计算位置与其实际位置之间的最大差异。 我设置的速度为62.5ms
,这使得最大漂移为球大小的四分之一。 坦白说,这比我想要的要快一点,但是任何比那慢的速度都将不够准确,并且可能导致无法检测到有效的匹配项。
如果有某种CSS过渡的每帧回调事件,一切都会容易得多,但是没有-我们仅有的事件是transitionend
事件,该事件在过渡结束时触发,但这没有用。给我们在这里。
但是无论如何-我们现在有一个游戏! 尝试以下完成的原型,看看您如何进行-游戏的目标是匹配每个目标,直到棋盘清晰为止 :
超越原型
当您完成时,什么也没有发生,只有一轮! 这只是一个原型,即使如此,我们仍然可以进行改进。 例如,如果我们限制球的运动,从而不允许其触碰边缘,那将使游戏更具挑战性和前卫性。
因此,请很快加入本文的第二部分 ,作为结束语 ,我们将研究如何(以及实际上是否可以)进一步开发此原型,使其成为一个完善且可分发的游戏。
同时,您可以下载本文所有演示的zip文件:
From: https://www.sitepoint.com/a-study-in-experimental-game-development/