引言
昨天我們總算把Renderer完成啦~
今天我們來設計按鍵對應各項功能,
其中會呼叫各個我們前幾天完成的函數。
今天大致會有的按鍵功能有:
這些按鍵處理會寫在main的無窮迴圈中,每一幀都會讀取是否有對應按鈕按下,
若有,則進行對應動作(ex: 按下w,攝影機向前移動)
引入Renderer並初始化
/* File: main.c */
#include <stdio.h> // 標準輸入輸出
#include <stdlib.h> // 標準函式庫
#include <windows.h> // 控制視窗用
#include <math.h> // 數學函式庫(三角函數、四捨五入等等)
#include "RenderMemory.h" // 繪製記憶體
#include "SystemSetting.h" // 系統設定
#include "GameStatus.h" // 遊戲狀態
// ------------新增部分-------------------
#include "Renderer.h" // 繪製器
#define M_PI acos(-1.0) // 因為cos(PI) = -1,所以用arccos(-1)來反向定義PI。
// 但此方法須引入"math.h"標頭檔
// p.s: PI將在旋轉角度的判讀上會用到
// --------------------------------------
int main(int argc, char *argv[])
{
init_render_memory(); // 先初始化memory
setting_system(); // 設置視窗屬性
initGameStatus(); // 初始化遊戲狀態(畫面是否更新初始化為True)
// ------------新增部分-------------------
init_renderer(); // 初始化Renderer,設置好所有相關變數值
// --------------------------------------
return 0;
}
主要無窮迴圈—更新畫面
當偵測到上一周期繪製記憶體被改變了(isFrameUpdated被設為True),
此區將被執行,清空畫面、重繪畫面。
/* File: main.c */
.
..
...
// 以下寫在init_renderer函數之下
int i = 0, j = 0; // 設置好給for迴圈使用的計數器
for(;;) /* 主要的無窮迴圈,每循環一次一個週期 */
{
if(isFrameUpdated) // 若上個週期更新了Renderer中的變數,
{ // 則isFrameUpdated已被設為True,此週期將清空原畫面並以新變數重繪
selfCls(); // 游標移到左上角,準備覆蓋舊畫面
for(i=0;i<SCREEN_HEIGHT;i++)
{
for(j=0;j<SCREEN_WIDTH;j++)
{
printf("%c", render_memory[i][j]); // 印出繪製記憶體中的畫面
}
}
// 以下部分為Debug用,可看出各項變數變化
printf("Camera x: %f\n", camera_x_pos);
printf("Camera y: %f\n", camera_y_pos);
printf("Camera z: %f\n", camera_z_pos);
printf("sin x: %f\n", sin_x);
printf("sin y: %f\n", sin_y);
printf("cos x: %f\n", cos_x);
printf("cos y: %f\n", cos_y);
printf("rotate x: %f\n", rot_x);
printf("rotate y: %f\n", rot_y);
printf("FOV: %f\n", fov);
isFrameUpdated = False; // 將上一周期被設為True的isFrameUpdated設回False
}
}
主要無窮迴圈—按鍵讀取(控制攝影機)
此部分為每一週期皆執行,讀取按鍵的訊息,執行不同動作。
當此週期按下按鍵,使Renderer中的變數更改後,會將isFrameUpdated設為True,
則下一週期將會執行以上更新畫面的部分。
反過來說,只要不按按鍵、不改變變數值,則畫面皆不會更新。
在進入程式碼前,關於w,a,s,d的往前、往後、往左、往右,以及方向鍵的旋轉,我想先做點補充:
先講以方向鍵來控制的旋轉吧!
C語言中,"math.h"標頭檔中所定義的三角函數,參數預設是使用弧度(Radian)哦!
簡單來說,就是把0~360度換成0~ 來算,轉換公式就是 。舉個例子: 轉成弧度就會是 也就是 ,而在這支程式裡我們已經定義了 ,也就是M_PI這個macro囉!這樣一來我們等一下就能用 來描述我們要表示的角度了。再來,我們來講講往前、往後、往左、往右,再直覺上來說,大家可能會認為這部分的處理,只要單純:
camera_x_pos += 5; // 例如此例子是往右5單位
之類的就行了吧?
但我想說的是,攝影機可能已經事先按了方向鍵「旋轉」過了,在我們目前的座標系中,想要往「旋轉過的座標系的右方」移動的話,必須先將該原始移動方向乘上適當的三角函數值。 感覺很抽象吧!我們來看以下的圖:
其實如果在攝影機已旋轉 度的情況下,要向右移動5的處理就會變成:
camera_x_pos += 5 * cos(theta);
camera_z_pos += 5 * sin(theta);
這就會做到角度旋轉後的修正了!
那麼以下就是程式碼囉!
/* File: main.c */
for(;;) /* 主要的無窮迴圈,每循環一次一個週期 */
{
if(isFrameUpdated) // 若上個週期更新了Renderer中的變數,
{ // 則isFrameUpdated已被設為True,此週期將清空原畫面並以新變數重繪
selfCls(); // 游標移到左上角,準備覆蓋舊畫面
for(i=0;i<SCREEN_HEIGHT;i++)
{
for(j=0;j<SCREEN_WIDTH;j++)
{
printf("%c", render_memory[i][j]); // 印出繪製記憶體中的畫面
}
}
// 以下部分為Debug用,可看出各項變數變化
printf("Camera x: %f\n", camera_x_pos);
printf("Camera y: %f\n", camera_y_pos);
printf("Camera z: %f\n", camera_z_pos);
printf("sin x: %f\n", sin_x);
printf("sin y: %f\n", sin_y);
printf("cos x: %f\n", cos_x);
printf("cos y: %f\n", cos_y);
printf("rotate x: %f\n", rot_x);
printf("rotate y: %f\n", rot_y);
printf("FOV: %f\n", fov);
isFrameUpdated = False; // 將上一周期被設為True的isFrameUpdated設回False
}
// ------------新增部分-------------------
/* 此部分在前幾天講Windows API時有提到,GetAsyncKeyState函數能取得參數按鍵是否被按的訊號 */
if(GetAsyncKeyState(87) != 0) /*W*/ //前進 // W是否被按下
{
render_screen(_CLEAN_MODE_); // 清空舊有畫面
camera_z_pos += cos_y * camera_speed; // 在有旋轉角度的情形下,
camera_x_pos -= sin_y * camera_speed; // 直線前進需要考慮x, z方向分量
render_screen(_RENDER_MODE_); // 繪製
isFrameUpdated = True; // 下一週期將更新到畫面上
}
if(GetAsyncKeyState(83) != 0) /*S*/ //後退 // S是否被按下
{
render_screen(_CLEAN_MODE_);
camera_z_pos -= cos_y * camera_speed;
camera_x_pos += sin_y * camera_speed;
render_screen(_RENDER_MODE_);
isFrameUpdated = True;
}
if(GetAsyncKeyState(65) != 0) /*A*/ //左移 // A是否被按下
{
render_screen(_CLEAN_MODE_);
camera_z_pos -= sin_y * camera_speed;
camera_x_pos -= cos_y * camera_speed;
render_screen(_RENDER_MODE_);
isFrameUpdated = True;
}
if(GetAsyncKeyState(68) != 0) /*D*/ //右移 // D是否被按下
{
render_screen(_CLEAN_MODE_);
camera_z_pos += sin_y * camera_speed;
camera_x_pos += cos_y * camera_speed;
render_screen(_RENDER_MODE_);
isFrameUpdated = True;
}
if(GetAsyncKeyState(66) != 0) /*B*/ //上升 // B是否被按下
{
render_screen(_CLEAN_MODE_);
camera_y_pos -= 2; // 旋轉並不影響上下移動,直接更改攝影機y位置
render_screen(_RENDER_MODE_);
isFrameUpdated = True;
}
if(GetAsyncKeyState(78) != 0) /*N*/ //下降 // N是否被按下
{
render_screen(_CLEAN_MODE_);
camera_y_pos += 2;
render_screen(_RENDER_MODE_);
isFrameUpdated = True;
}
if(GetAsyncKeyState(VK_RIGHT) != 0 || GetAsyncKeyState(VK_LEFT) != 0)
{
render_screen(_CLEAN_MODE_);
if(GetAsyncKeyState(VK_RIGHT) != 0) // 沿y軸旋轉,類似頭左右擺動,由左右方向鍵控制
{
rot_y -= camera_speed * 0.006; // 0.006是一個調整過的參數,大家可以斟酌此數值
} // 讓旋轉幅度變大:加大此數值,反之則減小
if(GetAsyncKeyState(VK_LEFT) != 0)
{
rot_y += camera_speed * 0.006;
}
if(rot_y > M_PI * 2 || rot_y < -(M_PI * 2)) rot_y = 0.0; // 當旋轉到360度與
render_screen(_RENDER_MODE_); // -360度時,歸零
isFrameUpdated = True; // 確保可無限旋轉
}
if(GetAsyncKeyState(VK_UP) != 0 || GetAsyncKeyState(VK_DOWN) != 0)
{
render_screen(_CLEAN_MODE_);
if(GetAsyncKeyState(VK_UP) != 0) // 沿x軸旋轉,類似頭上下擺動,由上下方向鍵控制
{
rot_x -= camera_speed * 0.006;
}
if(GetAsyncKeyState(VK_DOWN) != 0)
{
rot_x += camera_speed * 0.006;
}
if(rot_x > M_PI / 10) rot_x = M_PI / 10; // 這邊我設定成上仰18度時就停止,
else if(rot_x < -(M_PI / 10)) rot_x = -(M_PI / 10); // 往下18度時也停止。
render_screen(_RENDER_MODE_);
isFrameUpdated = True;
}
if(GetAsyncKeyState(VK_ESCAPE) != 0) // 按下Esc時,遊戲結束!
{
return 0;
}
// --------------------------------------
}
測試
終於終於,我們把第一版3D遊戲完成啦~~
馬上就來測試看看吧!
執行後我們可以看到空白一片,以及所有初始化後的Debug數值。
這時大家可以按下w,a,s,d,方向鍵,b,n等按鍵,會發現我們一開始設計的大平台出現囉!
(大家可以旋轉、平移到合適觀察的角度)
可以試試不同角度! 再次整理一下按鍵清單:
按鍵 | 功能 |
---|---|
w | 攝影機前進 |
s | 攝影機後退 |
a | 攝影機左移 |
d | 攝影機右移 |
方向鍵左右 | 攝影機沿y軸轉動(左右擺頭) |
方向鍵上下 | 攝影機沿x軸轉動(上下擺頭) |
b | 攝影機上升 |
n | 攝影機下降 |
再一張不同角度
尾聲
我們把3D引擎第一版完成啦!
明天我們要加上方塊放置功能,敬請期待~