卷轴 缓冲绘制 超级玛丽demo5
难度预警!
卷轴的概念
在游戏开发中,卷轴(Scrolling)指游戏画面随玩家操作或时间推移而横向或纵向移动的视觉效果,如同展开画卷一般。
相对坐标和绝对坐标
我们制作游戏的时候,世界是很大的,窗口只能显示出一小部分。所以就有了卷轴。
我们始终把 要操作的英雄放在窗口的中心。
英雄的坐标为x,y 窗口的大小为 w,h
我们默认 窗口的左上角坐标为 窗口的坐标
得到窗口坐标(cx,cy)为: x-w/2 , y- h/2
有了窗口之后,假如我们的世界有地球这么大,我们之前进行碰撞检测是全世界检测,其实我们只用检测英雄周围的范围就可以了。
这个超级玛丽
窗口 高16格 宽15格
世界 高16格 宽70格
定比例尺 最小的一格方便计算我们定为50像素*50像素
一共得到 3500 * 750 的世界大小
在(11,5) w=4 h=2的障碍物
在(24,6)w=4 h=9的障碍物
缓冲绘制
我们之前是一个字符 一个字符 cout 打印出来的,这次直接把窗口内所有东西 都放在一个字符串中,一次性打印整个窗口的内容,就完成了缓冲绘制。
为了防止画面闪烁,我们直接在清空控制台后 马上打印整个字符串,闪烁的问题 就基本解决了。
(但是还是会有闪的一瞬间,目前还无法解决,后面我们会学习二次缓冲但是文字无法使用)
理论部分很简单 下面我们来实践一下。
超级玛丽demo5
各个函数的目录
我们上一节的末尾讲了 模块绘制但是 没有用代码来实现。
模块绘制
这里我们从draw函数下手 把模块绘制先改一下
在之前 我们的draw 函数的功能是 给世界中的方块 赋值为图素的编码
这里我们改为 模块绘制
给出左上角坐标 (x,y) 还有 宽w 高h 全部绘制出来一个整体
void GameDraw::Draw(int x, int y, int w,int h,int tsi)
{//第一个 左上角的定位点在地图中就行,其他的出界就不画for (int i = 0; i < h; i++) {for (int j = 0; j < w; j++) {if (x >= 0 && x < _w && y >= 0 && y < _h) {client[y * _w + x] = tsi;x++;}}y++;x -= w;}
}
卷轴
我们约定
h_max , w_max 是整个程序 给你的空间
_h , _w 是世界的大小
h, w 是窗口大小
在头文件里面加几个量,还有print 我们的缓冲绘制区。
GameDraw.h
#pragma once
struct GameDraw {int _max_w,_max_h;int _w, _h;int client[32 * 256];char print[256];char _ts[128];int _tslen;int ww = 70, wh = 16;int cx = 0, cy = 0, cw = 15, ch = 16;void init();//初始化各个数据bool SetTs(const char* ts);//设置图素bool SetSize(int w, int h);//设置窗口大小void Begin();//把窗口大小中的client清零void Draw(int x, int y, int w,int h,int tsi);//给client 编码画图void End();//把client中的数据显示到窗口上
};
GameDraw.h
#pragma once
struct GameDraw {int _max_w,_max_h;int _w, _h;int client[32 * 256];char print[(15 * 2 + 1)*16 +1];char _ts[128];int _tslen;int ww = 70, wh = 16;int cx = 0, cy = 0, cw = 15, ch = 16;void init();//初始化各个数据bool SetTs(const char* ts);//设置图素bool SetSize(int w, int h);//设置窗口大小void Begin();//把窗口大小中的client清零void Draw(int x, int y, int w,int h,int tsi);//给client 编码画图void End();//把client中的数据显示到窗口上
};
gamedraw.cpp
#include "GameDraw.h"
#include <iostream>void GameDraw::init()
{_max_w = 256;_max_h = 32;_ts[0] = " "[0];_ts[1] = " "[1];_tslen = 0;
}bool GameDraw::SetTs(const char* ts)
{if (ts == nullptr) {return false;}int i = 0;while (1) {if (ts[i] == '\0' || ts[i + 1] == '\0') {break;}_tslen++;_ts[_tslen * 2] = ts[i++];_ts[_tslen * 2 + 1] = ts[i++];}return true;
}bool GameDraw::SetSize(int w, int h)
{if (w<1 || h<1 || w> _max_w || h> _max_h) {return false;}_w = w;_h = h;return true;
}void GameDraw::Begin()
{for (int i = 0; i < _h; i++) {for (int j = 0; j < _w; j++) {client[i * _w + j] = 0;}}
}void GameDraw::Draw(int x, int y, int w,int h,int tsi)
{//第一个 左上角的定位点在地图中就行,其他的出界就不画for (int i = 0; i < h; i++) {for (int j = 0; j < w; j++) {if (x >= 0 && x < _w && y >= 0 && y < _h) {client[y * _w + x] = tsi;x++;}}y++;x -= w;}
}void GameDraw::End()
{int pos = cx + cy * _w;int size = 0;for (int i = 0; i < ch; i++) {for (int j = 0; j < cw; j++) {print[size++] = _ts[client[pos + i * _w + j] * 2];print[size++] = _ts[client[pos + i * _w + j] * 2 + 1];}print[size++] = '\n';}print[size] = 0;system("cls");std::cout << print;
}
源.cpp
#include <iostream>
#include"GameDraw.h"
#include<windows.h>using namespace std;struct OBJ {int x, y, w, h;int tsi;
};void main() {OBJ hero = { 0,0,1,1,3 };OBJ ground = { 0,15,70,1,5 };OBJ brick1 = { 11,5,4,2,5 };OBJ brick2 = { 24,6,4,9,5 };GameDraw gd;while (1) {gd.init();gd.SetSize(70,16);gd.Begin();gd.SetTs("〓▲★●■");gd.Draw(hero.x, hero.y, hero.w, hero.h, hero.tsi);gd.Draw(ground.x, ground.y, ground.w, ground.h, ground.tsi);gd.Draw(brick1.x, brick1.y, brick1.w, brick1.h, brick1.tsi);gd.Draw(brick2.x, brick2.y, brick2.w, brick2.h, brick2.tsi);Sleep(30);gd.End();if (GetAsyncKeyState('W')) {hero.y -= 1;}if (GetAsyncKeyState('S')) {hero.y += 1;}if (GetAsyncKeyState('A')) {hero.x -= 1;}if (GetAsyncKeyState('D')) {hero.x += 1;}//卷轴gd.cx = hero.x - gd.cw/2;gd.cy = hero.y - gd.ch/2;if (gd.cx < 0) gd.cx = 0;if (gd.cy < 0) gd.cy = 0;if (gd.cx + gd.cw > gd.ww) gd.cx = gd.ww - gd.cw;if (gd.cy + gd.ch > gd.wh) gd.cy = gd.wh - gd.ch;}}