在二维游戏中,只要我们按下键盘上的特定键(如方向键),就能操控小人向各个方向走动,小人的形态也会随着按键发生不同的变化。
这样的变化事实上就使用不同帧来实现的,一般来说,实现一个小人的基本行走,至少需要12帧,一共有前后左右四个方向,并且每个方向都有三个状态:迈左脚,迈右脚,双脚并拢。我们常常把所有帧都放在一张图片中,提高加载速度,这张图片被称为精灵表,在之前我们已经封装了精灵表的快读函数。现在,我们来考虑用精灵表来实现精灵行走的键盘响应事件。
我们封装了一个精灵类,定义了精灵的位置,贴图,索引,朝向等一系列信息。
使用wsad四个键,可以操控精灵往上下左右四个方向移动,当我们按下特定键后,我们修改当前精灵的朝向,并且对精灵的位置pos_x,pos_y进行修改。
在精灵绘制函数中,我们采用了一个计时器,画面每刷新20次,我们就更换一张精灵贴图,这样,我们同时修改精灵位置、方向和贴图,在画面上就呈现了精灵边走边摆动手脚的动画效果。
由于OpenGL的键盘回调函数中没有按键抬起事件,所以在判定精灵是否正在走动上,我们用了一些小技巧。
在键盘回调函数中,我们又定义了一个计时器,每一次按键后都让计时器加1,另外在精灵绘制函数中使用一个变量来存储50次刷新画面前,按键计时器的大小。我们通过比较刷新前后按键计时器的大小是否相等,我们判断出精灵是否静止。
这相当于,我们有一个探测器,每隔50次画面刷新,我们就去探测键盘回调函数是否被调用,如果被调用了,那么说明按键处于激活状态,精灵在走动过程中,这时候,前后的计时器大小不相等;如果没有被调用,那么说明按键处于不被激活的状态,精灵处于静止状态,这时候,前后的计时器大小相等。
当然,如果计时器不断增加,那么会出现越界的现象,这并不影响我们的判断(数字越界后会变成负数而已),所以在这里没有特别处理。如果希望优化的话,也可以考虑适当地利用%。
使用到的精灵表(图片来自网络):
test.h
#pragma once
#define GLUT_DISABLE_ATEXIT_HACK
#include "GL/GLUT.H"
void loadTex(int i, char *filename, GLuint* texture);//一般纹理
void loadTex(int i, char *filename, GLuint* texture, unsigned char* backgroundColor);//透明纹理
class sprite
{
public:
//精灵位置(中心位置)
float pos_x;
float pos_y;
//帧动画参数
int num = 96;//一共多少帧
int col = 12;//一行有多少帧
//精灵索引下标
//前、左、右、后
int index[4][3][2];
//步长
float step;
//用于计数
int count;
int count2;
int count3;
//精灵贴图
GLuint texture;
//行走方向(枚举量)
typedef enum { left, right, front, back }direction;
//是否停止
bool isStop = true;
//行走方向
direction dir = front;
sprite(int _col,int _num,float x,float y,GLuint _texture,int* index,float _step);
//快速索引绘制精灵
void drawRect(GLuint texture, int i, int j);
//绘制精灵
void drawSprite();
};
texture.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<windows.h>
#include"test.h"
#define BITMAP_ID 0x4D42
//读纹理图片
static unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER *bitmapInfoHeader)
{
FILE *filePtr; // 文件指针
BITMAPFILEHEADER bitmapFileHeader; // bitmap文件头
unsigned char *bitmapImage; // bitmap图像数据
int imageIdx = 0; // 图像位置索引
unsigned char tempRGB; // 交换变量
// 以“二进制+读”模式打开文件filename
filePtr = fopen(filename, "rb");
if (filePtr == NULL) {
printf("file not open\n");
return NULL;
}
// 读入bitmap文件图
fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
// 验证是否为bitmap文件
if (bitmapFileHeader.bfType != BITMAP_ID) {
fprintf(stderr, "Error in LoadBitmapFile: the file is not a bitmap file\n");
return NULL;
}
// 读入bitmap信息头
fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);
// 将文件指针移至bitmap数据
fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);
// 为装载图像数据创建足够的内存
bitmapImage = new unsigned char[bitmapInfoHeader->biSizeImage];
// 验证内存是否创建成功
if (!bitmapImage) {
fprintf(stderr, "Error in LoadBitmapFile: memory error\n");
return NULL;
}
// 读入bitmap图像数据
fread(bitmapImage, 1, bitmapInfoHeader->biSizeImage, filePtr);
// 确认读入成功
if (bitmapImage == NULL) {
fprintf(stderr, "Error in LoadBitmapFile: memory error\n");
return NULL;
}
//由于bitmap中保存的格式是BGR,下面交换R和B的值,得到RGB格式
for (imageIdx = 0; imageIdx < bitmapInfoHeader->biSizeImage; imageIdx += 3) {
tempRGB = bitmapImage[imageIdx];
bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
bitmapImage[imageIdx + 2] = tempRGB;
}
// 关闭bitmap图像文件
fclose(filePtr);
return bitmapImage;
}
//读纹理图片
static unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER *bitmapInfoHeader, unsigned char* backgroundColor)
{
FILE *filePtr; // 文件指针
BITMAPFILEHEADER bitmapFileHeader; // bitmap文件头
unsigned char *bitmapImage; // bitmap图像数据
int imageIdx = 0; // 图像位置索引
// 以“二进制+读”模式打开文件filename
filePtr = fopen(filename, "rb");
if (filePtr == NULL) {
printf("file not open\n");
return NULL;
}
// 读入bitmap文件图
fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
// 验证是否为bitmap文件
if (bitmapFileHeader.bfType != BITMAP_ID) {
fprintf(stderr, "Error in LoadBitmapFile: the file is not a bitmap file\n");
return NULL;
}
// 读入bitmap信息头
fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);
// 将文件指针移至bitmap数据
fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);
// 为装载图像数据创建足够的内存
bitmapImage = new unsigned char[bitmapInfoHeader->biSizeImage];
// 验证内存是否创建成功
if (!bitmapImage) {
fprintf(stderr, "Error in LoadBitmapFile: memory error\n");
return NULL;
}
// 读入bitmap图像数据
fread(bitmapImage, 1, bitmapInfoHeader->biSizeImage, filePtr);
// 确认读入成功
if (bitmapImage == NULL) {
fprintf(stderr, "Error in LoadBitmapFile: memory error\n");
return NULL;
}
unsigned char* bitmapData; // 纹理数据
bitmapData = new unsigned char[bitmapInfoHeader->biSizeImage / 3 * 4];
int count = 0;
//添加alpha通道
for (imageIdx = 0; imageIdx < bitmapInfoHeader->biSizeImage; imageIdx += 3) {
bitmapData[count] = bitmapImage[imageIdx + 2];
bitmapData[count + 1] = bitmapImage[imageIdx + 1];
bitmapData[count + 2] = bitmapImage[imageIdx];
if (bitmapData[count] >= backgroundColor[0]
&& bitmapData[count + 1] >= backgroundColor[1]
&& bitmapData[count + 2] >= backgroundColor[2]) {
bitmapData[count + 3] = 0;
}
else bitmapData[count + 3] = 255;
count += 4;
}
// 关闭bitmap图像文件
fclose(filePtr);
return bitmapData;
}
//加载纹理的函数
void loadTex(int i, char *filename, GLuint* texture)
{
BITMAPINFOHEADER bitmapInfoHeader; // bitmap信息头
unsigned char* bitmapData; // 纹理数据
bitmapData = LoadBitmapFile(filename, &bitmapInfoHeader);
glBindTexture(GL_TEXTURE_2D, texture[i]);
// 指定当前纹理的放大/缩小过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D,
0, //mipmap层次(通常为,表示最上层)
GL_RGB, //我们希望该纹理有红、绿、蓝数据
bitmapInfoHeader.biWidth, //纹理宽带,必须是n,若有边框+2
bitmapInfoHeader.biHeight, //纹理高度,必须是n,若有边框+2
0, //边框(0=无边框, 1=有边框)
GL_RGB, //bitmap数据的格式
GL_UNSIGNED_BYTE, //每个颜色数据的类型
bitmapData); //bitmap数据指针
}
//加载纹理的函数
void loadTex(int i, char *filename, GLuint* texture, unsigned char* backgroundColor)
{
BITMAPINFOHEADER bitmapInfoHeader; // bitmap信息头
unsigned char* bitmapData; // 纹理数据
bitmapData = LoadBitmapFile(filename, &bitmapInfoHeader, backgroundColor);
glBindTexture(GL_TEXTURE_2D, texture[i]);
// 指定当前纹理的放大/缩小过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D,
0, //mipmap层次(通常为,表示最上层)
GL_RGBA, //我们希望该纹理有红、绿、蓝、alpha数据
bitmapInfoHeader.biWidth, //纹理宽带,必须是n,若有边框+2
bitmapInfoHeader.biHeight, //纹理高度,必须是n,若有边框+2
0, //边框(0=无边框, 1=有边框)
GL_RGBA, //bitmap数据的格式
GL_UNSIGNED_BYTE, //每个颜色数据的类型
bitmapData); //bitmap数据指针
}
sprite.cpp
#include"test.h"
sprite::sprite(int _col, int _num, float x, float y, GLuint _texture, int* _index,float _step)
{
count = count2 = count3 = 0;
col = _col;
num = _num;
pos_x = x;
pos_y = y;
texture = _texture;
int cnt = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 2; k++) {
index[i][j][k] = _index[cnt++];
}
}
}
step = _step;
}
void sprite::drawRect(GLuint texture, int i, int j)
{
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture); //选择纹理texture[status]
const GLfloat x1 = -0.5, x2 = 0.5;
const GLfloat y1 = -0.5, y2 = 0.5;
const GLfloat x = 1.0 / col, y = 1.0 / (num / col);
const GLfloat point[4][2] = { { x1,y1 },{ x2,y1 },{ x2,y2 },{ x1,y2 } };
const GLfloat dir[4][2] = { { j*x,1 - (i + 1)*y },{ (j + 1)*x,1 - (i + 1)*y },{ (j + 1)*x ,1 - i*y },{ j*x,1 - i*y } };
glBegin(GL_QUADS);
for (int k = 0; k < 4; k++) {
glTexCoord2fv(dir[k]);
glVertex2fv(point[k]);
}
glEnd();
glDisable(GL_TEXTURE_2D);
}
void sprite::drawSprite()
{
const int step = 50;
count++;
if (isStop) {
if (dir == front) {
drawRect(texture, index[0][1][0], index[0][1][1]);
}
else if (dir == back) {
drawRect(texture, index[3][1][0], index[3][1][1]);
}
else if (dir == left) {
drawRect(texture, index[1][1][0], index[1][1][1]);
}
else if (dir == right) {
drawRect(texture, index[2][1][0], index[2][1][1]);
}
}
else if (dir == front) {
if (count <= step) {
drawRect(texture, index[0][0][0], index[0][0][1]);
}
else if (count > step&&count <= step * 2) {
drawRect(texture, index[0][1][0], index[0][1][1]);
}
else if (count > step * 2 && count <= step * 3) {
drawRect(texture, index[0][2][0], index[0][2][1]);
}
}
else if (dir == back) {
if (count <= step) {
drawRect(texture, index[3][0][0], index[3][0][1]);
}
else if (count > step && count <= step * 2) {
drawRect(texture, index[3][1][0], index[3][1][1]);
}
else if (count > step * 2 && count <= step * 3) {
drawRect(texture, index[3][2][0], index[3][2][1]);
}
}
else if (dir == left) {
if (count <= step) {
drawRect(texture, index[1][0][0], index[1][0][1]);
}
else if (count > step && count <= step * 2) {
drawRect(texture, index[1][1][0], index[1][1][1]);
}
else if (count > step * 2 && count <= step * 3) {
drawRect(texture, index[1][2][0], index[1][2][1]);
}
}
else if (dir == right) {
if (count <= step) {
drawRect(texture, index[2][0][0], index[2][0][1]);
}
else if (count > step && count <= step * 2) {
drawRect(texture, index[2][1][0], index[2][1][1]);
}
else if (count > step * 2 && count <= step * 3) {
drawRect(texture, index[2][2][0], index[2][2][1]);
}
}
if (count%step == 0) {
if (count2 == count3) {
if (dir == front) {
drawRect(texture, index[0][1][0], index[0][1][1]);
}
else if (dir == back) {
drawRect(texture, index[3][1][0], index[3][1][1]);
}
else if (dir == left) {
drawRect(texture, index[1][1][0], index[1][1][1]);
}
else if (dir == right) {
drawRect(texture, index[2][1][0], index[2][1][1]);
}
isStop = true;
}
count3 = count2;
}
if (count == step * 3) {
count = 0;
}
}
main.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include<time.h>
#include <stdlib.h>
#include"test.h"
#define size 0.2f
#include <math.h> /* for cos(), sin(), and sqrt() */
GLuint texture[1];
//视区
float whRatio;
int wHeight = 0;
int wWidth = 0;
//视点
float center[] = { 0, 0, 0 };
float eye[] = { 0, 0, 5 };
sprite *s;
void drawScene()
{
glPushMatrix();
glTranslatef(s->pos_x, s->pos_y, 2);
glScalef(size, size, 1);
s->drawSprite();
glPopMatrix();
}
void updateView(int height, int width)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);//设置矩阵模式为投影
glLoadIdentity(); //初始化矩阵为单位矩阵
whRatio = (GLfloat)width / (GLfloat)height; //设置显示比例
glOrtho(-4, 4, -4, 4, -100, 100); //正投影
glMatrixMode(GL_MODELVIEW); //设置矩阵模式为模型
}
void key(unsigned char k, int _x, int _y)
{
s->count2++;
switch (k)
{
case 'a': {
s->dir = sprite::left;
s->isStop = false;
s->pos_x -= s->step;
break;
}
case 'd': {
s->dir = sprite::right;
s->isStop = false;
s->pos_x += s->step;
break;
}
case 'w': {
s->dir = sprite::back;
s->isStop = false;
s->pos_y += s->step;
break;
}
case 's': {
s->dir = sprite::front;
s->isStop = false;
s->pos_y -= s->step;
break;
}
}
updateView(wHeight, wWidth); //更新视角
}
void reshape(int width, int height)
{
if (height == 0) //如果高度为0
{
height = 1; //让高度为1(避免出现分母为0的现象)
}
wHeight = height;
wWidth = width;
updateView(wHeight, wWidth); //更新视角
}
void idle()
{
glutPostRedisplay();
}
void init()
{
srand(unsigned(time(NULL)));
glEnable(GL_DEPTH_TEST);//开启深度测试
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5);
unsigned char color[3] = { 255,255,255 };
glGenTextures(1, texture);
loadTex(0, "1.bmp", texture,color);
int index[] = {0,0,0,1,0,2,1,0,1,1,1,2,2,0,2,1,2,2,3,0,3,1,3,2};
s = new sprite(12, 96, -2.5f, -2.5f, texture[0], index,0.06f);
}
void redraw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清除颜色和深度缓存
glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); //初始化矩阵为单位矩阵
gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2], 0, 1, 0); // 场景(0,0,0)的视点中心 (0,5,50),Y轴向上
glPolygonMode(GL_FRONT, GL_FILL);
drawScene();//绘制场景
glutSwapBuffers();//交换缓冲区
}
int main(int argc, char *argv[])
{
glutInit(&argc, argv);//对glut的初始化
glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
//初始化显示模式:RGB颜色模型,深度测试,双缓冲
glutInitWindowSize(600, 600);//设置窗口大小
int windowHandle = glutCreateWindow("Simple GLUT App");//设置窗口标题
glutDisplayFunc(redraw); //注册绘制回调函数
glutReshapeFunc(reshape); //注册重绘回调函数
glutKeyboardFunc(key); //注册按键回调函数
glutIdleFunc(idle);//注册全局回调函数:空闲时调用
init();
glutMainLoop(); // glut事件处理循环
return 0;
}