飞船无论朝哪边行驶,都能通过结构体记录获取它的初始坐标、转向角度和在该方向行进的距离,需要根据这些信息计算飞船移动后的坐标。
向量(vector)指具有大小(magnitude)和方向(direction)的量,可以理解为有方向的线段。
标量或纯量(scalar)指只有大小没有方向的量。
向量的分量(component)是指向量在不同方向上的投影或分解。
二维向量
V
⃗
(
2
,
−
5
)
\vec{V}(2, -5)
V(2,−5),X 轴方向上分量为 2, Y 轴方向上分量为 -5,向量大小
∣
∣
V
⃗
∣
∣
||\vec{V}||
∣∣V∣∣ 由勾股定理得到
2
2
+
(
−
5
)
2
\sqrt{2^2 + (-5)^2}
22+(−5)2,书写参考 《Markdown 数学公式详解》https://blog.csdn.net/qq_34745941/article/details/126598575
单位向量(unit vector)为大小或模为1个单位的向量。一个非零向量除以它的模,就可以得到相应的单位向量。所以向量也可以记为标量乘以指定方向的单位向量,然后相加,如 (2,-5) 可以记为 2(1,0)-5(0,1)
向量还有一种记法是以大小和方向表示,这里的方向通常为向量线与 X 正轴的夹角,X 正轴逆时针方向夹角为正,顺时针方向夹角为负。由于屏幕坐标左上角为原点,屏幕范围为正轴,纵轴与通常坐标轴方向相反,Allegro 函数 al_rotate_transform 应用转换矩阵旋转时,弧度为正则是顺时针,为负则是逆时针。
为了便于计算,假设飞船初始朝向与 X 轴平行,绘制初始画面时,飞船朝上与 Y 轴平行,向量夹角为负 90 度,右转 30 度向前行驶一段距离,则飞船终点相对于初始坐标变化 X 坐标为 ∣ ∣ V ⃗ ∣ ∣ c o s ( 30 − 90 ) ||\vec{V}||cos(30-90) ∣∣V∣∣cos(30−90),Y 坐标为 ∣ ∣ V ⃗ ∣ ∣ s i n ( 30 − 90 ) ||\vec{V}||sin(30-90) ∣∣V∣∣sin(30−90)
C 语言 math 模块中的 cos 和 sin 传入参数也是弧度。
Allegro 中按住按键不放,并不会持续产生按键事件,可以在定时器事件中循环检查所有按键状态 al_get_keyboard_state,然后通过 al_key_down 判断指定按键按下状态,未检测到按下状态即为弹起 https://liballeg.org/a5docs/trunk/keyboard.html#al_get_keyboard_state ,类似 pygame.key.get_pressed https://www.pygame.org/docs/ref/key.html#pygame.key.get_pressed
spaceship.h
#ifndef _SPACESHIP_H
#define _SPACESHIP_H
#include <allegro5/allegro.h>
extern const int WIDTH;
extern const int HEIGHT;
typedef struct {
float sx; // 飞船中屏幕中的坐标
float sy;
float heading; //飞船朝向角度,如 30 度为 30.0
float speed;
int gone; // 是否阵亡
ALLEGRO_COLOR color;
} Spaceship;
void draw_ship(Spaceship *s);
void rotate_ship(Spaceship *s, int direction);
void accelerate_ship(Spaceship *s, int gas);
#endif
spaceship.c
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include <allegro5/allegro_primitives.h>
#define DEGREES(x) ((x) * ALLEGRO_PI / 180.0)
#define DEFAULT_DEGREE 10.0
#define REAL_ROTATE(x) ((x) - 90.0)
#define DEFAULT_SPEED 0.1
#define MAX_SPEED 3.0
#include "./spaceship.h"
// 绘制飞船
void draw_ship(Spaceship *s) {
ALLEGRO_TRANSFORM transform;
al_identity_transform(&transform);
al_rotate_transform(&transform, DEGREES(s->heading));
al_translate_transform(&transform, s->sx, s->sy);
al_use_transform(&transform);
// 画线需要在调用 al_create_display 前设置多重采样以抗锯齿
al_draw_line(-8, 9, 0, -11, s->color, 3.0f);
al_draw_line(0, -11, 8, 9, s->color, 3.0f);
al_draw_line(-6, 4, -1, 4, s->color, 3.0f);
al_draw_line(6, 4, 1, 4, s->color, 3.0f);
// 重置变换矩阵,不然会影响其他绘制内容
al_identity_transform(&transform);
al_use_transform(&transform);
// 画辅助线
al_draw_line(s->sx, 0, s->sx, HEIGHT, al_map_rgb(255, 255, 255), 0.3f);
al_draw_line(0, s->sy, WIDTH, s->sy, al_map_rgb(255, 255, 255), 0.3f);
}
// 飞船左右控制转向, direction -1-左转 1-右转
void rotate_ship(Spaceship *s, int direction) {
if (direction == 0) return;
s->heading += DEFAULT_DEGREE * direction;
if (abs(s->heading) >= 360.0) {
s->heading -= 360.0 * direction;
}
}
// 飞船上下控制加减速 gas -1-减速 1-加速,这里飞船速度为每帧前进距离
void accelerate_ship(Spaceship *s, int gas) {
if (gas == 0) return;
s->speed += DEFAULT_SPEED * gas;
if (s->speed < 0.0) {
s->speed = 0.0;
return;
}
if (s->speed > MAX_SPEED) {
s->speed = MAX_SPEED;
}
s->sx += s->speed * cos(DEGREES(REAL_ROTATE(s->heading)));
s->sy += s->speed * sin(DEGREES(REAL_ROTATE(s->heading)));
printf("Rotate:%.2f X:%.2f Y:%.2f SPEED:%.2f\n", REAL_ROTATE(s->heading), s->sx, s->sy, s->speed);
}
main.c
#include <stdio.h>
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include "./utility.h"
#include "./spaceship.h"
// 全局变量
const int WIDTH = 800; // 屏幕尺寸
const int HEIGHT = 600;
const int FPS = 60; // 帧率
ALLEGRO_FONT *font_40; // 字体
int main() {
// 初始化 Allegro
if (!al_init()) {
fprintf(stderr, "Failed to initialize Allegro!\n");
return -1;
}
// 安装键盘驱动,安装成功或已经安装过则返回 true
if (!al_install_keyboard()) {
fprintf(stderr, "Failed to initialize keyboard!\n");
return -1;
}
// 初始化图形绘制插件
if (!al_init_primitives_addon()) {
fprintf(stderr, "Failed to initialize primitives addon!\n");
return -1;
}
// 初始化字体插件
if (!al_init_font_addon()) {
fprintf(stderr, "Failed to initialize font addon!\n");
return -1;
}
// 初始化 TTF 字体插件
if (!al_init_ttf_addon()) {
fprintf(stderr, "Failed to initialize ttf font addon!\n");
return -1;
}
// 加载 TTF 字体,字号 40
font_40 = al_load_ttf_font("arial.ttf", 40, 0);
// 启用多重采样
al_set_new_display_option(ALLEGRO_SAMPLE_BUFFERS, 1, ALLEGRO_SUGGEST);
al_set_new_display_option(ALLEGRO_SAMPLES, 4, ALLEGRO_SUGGEST);
// 创建指定宽高的窗口
ALLEGRO_DISPLAY *display = al_create_display(WIDTH, HEIGHT);
if (!display) {
fprintf(stderr, "Failed to create display!\n");
return -1;
}
// 创建事件队列
ALLEGRO_EVENT_QUEUE *event_queue = al_create_event_queue();
if (!event_queue) {
fprintf(stderr, "Failed to create event queue!\n");
al_destroy_display(display);
return -1;
}
// 创建定时器,每 1.0 / FPS 秒触发一次
ALLEGRO_TIMER *timer = al_create_timer(1.0 / FPS);
// 注册事件源到事件队列
al_register_event_source(event_queue, al_get_display_event_source(display));
al_register_event_source(event_queue, al_get_keyboard_event_source());
al_register_event_source(event_queue, al_get_timer_event_source(timer));
// 清除屏幕并填充黑色
al_clear_to_color(al_map_rgb(0, 0, 0));
// 绘制背景 logo
draw_logo();
// 初始化并绘制飞船
Spaceship s = {WIDTH / 2.0, HEIGHT / 2.0, 0.0, 0.0, 0, al_map_rgb(0, 255, 0)};
draw_ship(&s);
// 交换缓冲区
al_flip_display();
// 启动定时器
al_start_timer(timer);
bool done = false; // 游戏是否结束
bool redraw = false; // 是否重绘
ALLEGRO_KEYBOARD_STATE key_state; // 按键状态
int rotate = 0; // 转向
int accelerate = 0; // 加速
// 轮询事件
while (!done) {
ALLEGRO_EVENT event;
// 等待从事件队列取出事件
al_wait_for_event(event_queue, &event);
if (event.type == ALLEGRO_EVENT_TIMER) {
// 处理定时器事件
redraw = true; // 重绘
// 获取所有按键状态并保存到 key_state
al_get_keyboard_state(&key_state);
// 如果按下左右键就转向,否则左右键都为弹起状态则方向不变
if (al_key_down(&key_state, ALLEGRO_KEY_LEFT)) {
rotate = -1;
} else if (al_key_down(&key_state, ALLEGRO_KEY_RIGHT)) {
rotate = 1;
} else {
rotate = 0;
}
// 按上键加速,上键弹起或按下键减速
if (al_key_down(&key_state, ALLEGRO_KEY_UP)) {
accelerate = 1;
} else {
accelerate = -1;
}
if (al_key_down(&key_state, ALLEGRO_KEY_DOWN)) {
accelerate = -1;
}
} else if (event.type == ALLEGRO_EVENT_KEY_DOWN && event.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
// 处理单次按键事件,这里响应 ESC 按键
done = true;
} else if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
// 处理窗口事件,这里响应点击窗口右上角关闭
done = true;
}
if (redraw && al_is_event_queue_empty(event_queue)) {
redraw = false;
// 更新转向和加速状态
rotate_ship(&s, rotate);
accelerate_ship(&s, accelerate);
// 清屏并更新绘制
al_clear_to_color(al_map_rgb(0, 0, 0));
draw_logo();
draw_ship(&s);
al_flip_display();
}
}
// 销毁资源,释放内存
al_destroy_timer(timer);
al_destroy_font(font_40);
al_destroy_display(display);
al_destroy_event_queue(event_queue);
return 0;
}