二、游戏实现
2.1绘图部分
该游戏为实时性游戏,为保证流畅,采用了游戏常用的60fps的帧率。每一帧绘制的内容包括背景图片、当前比 分、火柴人以及羽毛球。
下面分别介绍绘图部分的各个细节:
半透明图片
常见的GDI库只能绘制BMP图片,其附带的透明选项也只是选取图片左上角像素作为透明颜色,并不能实现真 正的半透明绘图,画出的图片常会带有丑陋的白边。
为了解决这一问题,我们使用了GDI+库,它支持各种各样的图片格式,当然也就包括含有透明通道的PNG格 式。有了GDI+,我们也就实现了更为精美的游戏画面。
双缓冲绘图
绘制出图片并完成一些简单的动画后,我们发现在刷新一帧时,画面会出现闪烁。想到以前使用MFC编程时 曾接触过双缓冲绘图方法。画面出现闪烁的原因是绘图时多次把像素转移到屏幕上,而双缓冲可以先在内存 中建立起一块画布,等到在内存中全部绘完,再调用BitBlt函数将像素逐个复制到屏幕上。这样便避免了画面 的闪烁。
局部刷新
GDI+固然比GDI功能强大,但它绘图效率低是不容忽视的问题。与Direct2D使用显卡绘图不同,GDI+使用的 是CPU,再加上我们的游戏需要绘制很多元素,对绘图优化就显得很有必要。我们注意到,切换一帧时,并 不是全部元素都需要重新绘制,因此也就可以使用局部刷新技术,只重绘更新了的区域。
人物动画
在FLASH中,开发者可添加一种名为影片剪辑的元素,它可以做与主时间轴异步的动画,影片剪辑可以极大地 方便动画的制作。然而,汇编语言并没有强大的动画制作工具,实现精细的动画也就需要很复杂的处理。
例如,绘制人物时,手臂挥拍、脚步移动、跳起时阴影保留在地面上,这些是各自独立的。我们将小人的图 像拆解开来,分别画四个部分,这就实现了游戏中小人复杂的运动。
图片旋转
游戏中的羽毛球并不是圆球,因此也就涉及到了旋转。我们并没有简单地制作许多张不同角度的羽毛球素材,而是只用了一张图片。
使用GDI+中的函数GdipImagePointsI,可以以图片左上、右上、左下三点为基准,规定三个新的基准点, 对原有矩形做线性变换,以此来完成图像的旋转。
2.2逻辑部分
运动模型
现实中,羽毛球由于受到的空气阻力较大,其的运动轨迹不同于其他球类,不呈现抛物线。我们在游戏中为 了还原真实性,使用了如下运动方程:
式子中,(\gamma)为空气的粘滞系数。
碰撞判定
羽毛球涉及的碰撞判定有很多:球网、地面、墙壁(球可以反弹回来!)都具有碰撞判定。在游戏中,球的运动是 离散、不连续的,因此在每一帧计算时,球的判定点很难恰好处于墙壁等障碍物的平面上。对于球撞击障碍物反弹的动作,我们的处理方法是:
使球的坐标做关于平面的对称变换使球垂直于平面方向的速度分量反向
不同平面有不同的弹力系数,对应于不同的反弹速度有了这样的处理,球的运动才会精确反弹。
击球判定 击球是游戏中角色最主要的动作。游戏中击球的判定也很独特:人的可击球区域是球拍面挥动起来可达到的 扇形圆环区域,羽毛球飞出的角度与速度是在击球瞬间由人与球的相对位置决定的。此外,根据球的高度不 同,角色也可以自动判断应该从上方还是从下方击球。
AI
AI的设计可以说是本游戏的一个亮点。游戏中,分别有两个函数leftAIconsider与rightAIconsider,这些函 数会根据包括羽毛球位置在内的游戏状态信息,指挥小人的行为(移动、跳跃、击球)。
游戏内置了两个不同难度的AI,玩家亦可自行修改AI决策函数,利用AI-AI对战模式与内置的AI进行PK。
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes" />
<title>
Stick Figure Badminton
</title>
<link href="css/materialize.min.css" rel="stylesheet" type="text/css">
<style type="text/css">
canvas:focus {
outline: none;
}
canvas {
width: 100%;
box-shadow: 0 5px 11px 0 rgba(0,0,0,0.18), 0 4px 15px 0 rgba(0,0,0,0.15);
}
body {
background-image: url(img/bg.gif);
display: flex;
min-height: 100vh;
flex-direction: column;
}
main {
display: flex;
flex: 1 0 auto;
align-items: center;
justify-content: center;
margin-top: 1rem!important;
margin-bottom: 1rem!important;
}
textarea {
font-family: "courier", "consolas"!important;
}
</style>
</head>
<body>
<audio id="music" src="bgm/bgm.mp3" loop="loop"></audio>
<audio id="hit" src="bgm/hit.wav"></audio>
<main class="container">
<canvas id="canv" tabindex="0" width="990px" height="660px">Please use Chrome</canvas>
</main>
<div id="buttons" class="row">
<div class="col s12">
<a class="waves-effect waves-light btn" onclick="editAI(1);">Edit left AI</a>
<a class="waves-effect waves-light btn" onclick="editAI(2);">Edit right AI</a>
</div>
</div>
<div id="modal1" class="modal modal-fixed-footer">
<div class="modal-content">
<h4>Left AI</h4>
<div class="input-field">
<textarea id="textarea1" class="materialize-textarea"></textarea>
</div>
</div>
<div class="modal-footer">
<a href="#!" class="modal-action modal-close waves-effect waves-green btn-flat " onclick="confirmAI(1);">Confirm</a>
</div>
</div>
<div id="modal2" class="modal modal-fixed-footer">
<div class="modal-content">
<h4>Right AI</h4>
<div class="input-field">
<textarea id="textarea2" class="materialize-textarea"></textarea>
</div>
</div>
<div class="modal-footer">
<a href="#!" class="modal-action modal-close waves-effect waves-green btn-flat " onclick="confirmAI(2);">Confirm</a>
</div>
</div>
<div class="fixed-action-btn" style="bottom: 45px; right: 24px;">
<a id="menu" class="white waves-effect waves-light btn-large btn-floating " onclick="$('.tap-target').tapTarget('open');">
<!-- <i class="material-icons">menu</i> -->
<svg aria-hidden="true" class="octicon octicon-mark-github hvr-grow-rotate" height="56" version="1.1" viewBox="0 0 16 16" width="56">
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path>
</svg>
</a>
</div>
<div class="tap-target teal lighten-2 white-text" data-activates="menu">
<div class="tap-target-content">
<h5>Authors</h5>
<a class="white-text" href="https://github.com/b-z/StickFigureBadminton">
GitHub page
<i class="material-icons" style="font-size:1rem;">link</i>
</a>
</div>
</div>
</body>
<script type="text/javascript" src="js/jquery-2.1.3.min.js"></script>
<script type="text/javascript" src="js/materialize.min.js"></script>
<script type="text/javascript" src="js/main.js"></script>
</html>