草庐IT

C语言跨平台游戏开发

=Freiheit= 2024-06-02 原文

通常我们认为,纯C(即不使用C艹)很难实现跨平台的游戏。这是由于它支持的图形库非常少,一般需要调用系统句柄才能进行图形化。但是很显然这是一个及其费时费力还容易出错的方式。所以,在这篇文章里,我希望给大家介绍一些比较轻松的制作C语言的跨平台游戏的方式。

I. 游戏引擎的选择

引擎绝对是个好东西,它帮你简化了许多繁琐而重复的构建阶段的实现。所以我们通常希望有一个好用的引擎。这里有我能找到的所有跨平台C语言库。

  • Curses: 这是一个非常之古老的图形库,只支持命令行的图形编成。好在它学习简单、轻量便捷、函数丰富,它依然是新手学习游戏逻辑最佳的选择。它支持Windos(Ncurcesw), Linux(Ncurses,curses), MacOS(Ncurses), 移动端大部分的终端模拟器, 和你能找到的许多古老系统(如Dos)。
  • SDL: 额,这位老朋友就不用介绍了吧。如果有些游戏编程的基础,它会让你感到很亲切。它应该支持所有有图形界面的操作系统。
  • OpenGL: 如果你是一位C语言高手,想做高质量3D游戏,不要犹豫,就是它了。它同样支持所有有图形界面的操作系统。因为OpenGL是一个“标准”,由你的显卡生产商实现,所以使用时需借助一个窗口库和一个函数查找库来编程。你可以感受到搭建编辑环境所带来的痛苦,非常不推荐新手使用
  • 其他:TBOX LCUI, 这两个都是跨平台的。

II. 游戏逻辑的研究

不管你采用了哪个图形库,游戏逻辑都不会变化太多。以下是一个适用与所有游戏的游戏逻辑。

  1. 导入图形库
  2. 读入所有信息、模型
  3. 初始化所有设置,显示开始界面
  4. 开始游戏主循环:输入、处理、绘制、刷新、暂停等
  5. 达到退出条件,显示结束界面
  6. 保存所有信息
  7. 退出

这套流程/逻辑可以用于几乎所有游戏。

其实很推荐使用流程图来理清思路。

特别要注意各种“边界”值。即你想要把数轴的原点归为哪边?这个问题可以用文氏图来解决。

III. 游戏编程的推荐编程范式 和 方式

虽说编程范式没有最好与最坏之分,但绝对有适合一定领域的编程范式。这里,我们通常建议使用函数式编程。

编程方式,即编程规范。有些人认为开发过程越快就越好,可事情远没有这么简单。比如你为了快速开发使用了a,b,c ...... x,y,z共26个变量,结果你有一段时间去做别的项目了,回来拾起旧项目时一脸蒙蔽:"这都啥玩意阿?"。所以,应该把代码写得易于阅读,层次分明,注释有用。(这里表扬Python)

IV. 一个“简单”的例子

这是一个“简单”的打飞机游戏,使用C语言和Ncurses库开发。不管你承认与否,这真的是一个开始游戏编程的极好的地方。

编译时记得链接上你的Ncurses库

/*
Finished Version 0.1
Complitet: cd Desktop && gcc -o test01.out Shooter01.c -lncurses && ./test01.out
*/

#include <ncurses.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


/* Global */
int HEIGHT, WIDTH, MAP_Y, MAP_X, MAP_HEIGHT, MAP_WIDTH, Timer, Hp, Score, Energy;

struct Pos *Bullets;
struct Pos *Enemies;
int Bullets_len, Bullets_full;
int Enemies_len, Enemies_full;


/* == Structs ==  */
struct Pos
{
	int x;
	int y;
};


/* == Tools ==  */

/* == Starting & Ending == */
void starting(void)
{
	initscr();
	
	// 准备工作
	noecho(); // 取消回显
    raw(); // 取消缓冲区
    nodelay(stdscr, 1); // 对于getch()不等待
    keypad(stdscr, 1); // 可以读取特殊字符
    curs_set(0); // 光标不可见
    getmaxyx(stdscr, HEIGHT, WIDTH); // 获取屏幕长宽
    
    // 初始化游戏
    Timer = 0;
    Hp = 3;
    Score = 0;
    Energy = 100;
    
    MAP_HEIGHT = 30;
    MAP_WIDTH = 42;
    
    Bullets_len =30;
	Bullets_full=0;
	Bullets = (struct Pos *)malloc(Bullets_len * sizeof(struct Pos));
	for(int bullet=0; bullet<Bullets_len; bullet++)
	{
		Bullets[bullet].x=-1;
		Bullets[bullet].y=-1;
	}
	
	Enemies_len =30;
	Enemies_full=0;
	Enemies = (struct Pos *)malloc(Enemies_len * sizeof(struct Pos));
	for(int enemy=0; enemy<Enemies_len; enemy++)
	{
		Enemies[enemy].x=-1;
		Enemies[enemy].y=-1;
	}
    
    MAP_Y = 2;
    MAP_X = (WIDTH-MAP_WIDTH)/2;
    
    // 开始界面
    char title[] = "= Shooter v0.1 =";
    mvprintw(HEIGHT/2, (WIDTH-strlen(title))/2, "%s", title);
    char message[] = "[Press any key to start]";
    mvprintw(HEIGHT-3, (WIDTH-strlen(message))/2, "%s", message);
    char tips[] = "Moving: W A (Energy-1)          Shooting: J (Energy-5)          Quit: Q          Get Energy by killing enemies!";
    mvprintw(HEIGHT-1, (WIDTH-strlen(tips))/2, "%s", tips);
    refresh();
    
    while(getch()==ERR)
    {
    	usleep(1000);
    }
    clear(); //清屏
    refresh();
}


void ending(void)
{
	
	// 结束界面
    char title[] = "= Game Over =";
    mvprintw(HEIGHT/2, (WIDTH-strlen(title))/2, "%s", title);
    char message[] = "[Press any key to quit]";
    mvprintw(HEIGHT-2, (WIDTH-strlen(message))/2, "%s", message);
    refresh();
    
    while(getch()==ERR)
    {
    	usleep(1000);
    }
    clear(); //清屏
    refresh();
	endwin();
	
	exit(0);
}


/* Operation */
// 出界判定
int on_map(int x, int y)
{
	if(x<0 || x>=MAP_WIDTH || y<0 || y>=MAP_HEIGHT)
	{
		return 0;
	}
	else return 1;
}


void new_bullet(struct Pos player)
{
	// 加大内存
	if(Bullets_len==Bullets_full)
	{
		Bullets = realloc(Bullets, Bullets_len + 30*sizeof(struct Pos));
		for(int index=Bullets_len; index<Bullets_len+30; index++)
		{
			Bullets[index].x=-1;
			Bullets[index].y=-1;
			
		}
	}
	// 新建子弹
	for(int index=0; index<Bullets_len; index++)
	{
		if(Bullets[index].x==-1) //x=-1就算空位
		{
			Bullets[index].x=player.x+1;
			Bullets[index].y=player.y-1;
			Bullets_full++;
			break;
		}
	}
}

// 销毁子弹
void destroy_bullet(int bullet)
{
	Bullets[bullet].x=-1;
	Bullets[bullet].y=-1;
	Bullets_full--;
}

// 移动子弹
void move_bullets(void)
{
	for(int bullet=0; bullet<Bullets_len; bullet++)
	{
		if(on_map(Bullets[bullet].x, Bullets[bullet].y) && Bullets[bullet].x!=-1)
		{
			Bullets[bullet].y--;
		}
		else if(Bullets[bullet].x!=-1)
		{
			destroy_bullet(bullet);
			continue;
		}
		
	}
}


void new_enemies(int sum)
{
	// 加大内存
	if(Enemies_len==Enemies_full)
	{
		Enemies = realloc(Enemies, Enemies_len + 30*sizeof(struct Pos));
		for(int index=Enemies_len; index<Enemies_len+30; index++)
		{
			Enemies[index].x=-1;
			Enemies[index].y=-1;
			
		}
	}
	// 新建对手
	for(int index=0; index<Enemies_len; index++)
	{
		if(Enemies[index].x==-1) //x=-1就算空位
		{
			srand(Timer);
			
			Enemies[index].x=rand()%MAP_WIDTH;
			Enemies[index].y=0;
			Enemies_full++;
			break;
		}
	}
}

// 销毁对手
void destroy_enemy(int enemy)
{
	Enemies[enemy].x=-1;
	Enemies[enemy].y=-1;
	Enemies_full--;
	Score += 1;
}

// 移动对手
void move_enemies(void)
{
	for(int enemy=0; enemy<Enemies_len; enemy++)
	{
		if(on_map(Enemies[enemy].x, Enemies[enemy].y))
		{
			Enemies[enemy].y++;
		}
		else if(Enemies[enemy].x!=-1)
		{
			destroy_enemy(enemy);
		}
		
	}
}

// 撞击判定
void judge_hits(struct Pos player)
{
	for(int enemy=0; enemy<Enemies_len; enemy++)
	{
		if(on_map(Enemies[enemy].x, Enemies[enemy].y))
		{
			for(int bullet=0; bullet<Bullets_len; bullet++)
			{
				if(on_map(Bullets[bullet].x, Bullets[bullet].y))
				{
					if(abs(Bullets[bullet].x-Enemies[enemy].x)<=1 && Bullets[bullet].y==Enemies[enemy].y) // 对手与子弹
					{
						destroy_bullet(bullet);
						destroy_enemy(enemy);
						Score+=99;
						Energy+=10;
					}
				}
			}
			
			// 玩家与对手
			if(abs(player.x+1-Enemies[enemy].x)<=1 && player.y==Enemies[enemy].y)
			{
				Hp-=1;
				destroy_enemy(enemy);
				Score-=99999;
				Energy+=100;
			}
		}
	}	
	
}


/* == Drawing == */

// 画地图
void draw_map(void)
{
	mvprintw(MAP_Y-1, MAP_X-1, "###########################################");
	
	for(int index=MAP_Y-1; index<=MAP_Y+MAP_HEIGHT; index++)
	{
		mvprintw(index, MAP_X-1, "#");
		mvprintw(index, MAP_X+MAP_WIDTH, "#");
	}
	
	mvprintw(MAP_Y+MAP_HEIGHT, MAP_X-1, "###########################################");
}

// 画玩家
void draw_player(struct Pos pos, struct Pos last_pos)
{
	char player[]="/|\\";
	mvprintw(MAP_Y+pos.y, MAP_X+pos.x, "%s", player);
}

// 画子弹
void draw_bullets(void)
{
	for(int bullet=0; bullet<Bullets_len; bullet++)
	{
		if(on_map(0, Bullets[bullet].y))
		{
			mvprintw(MAP_Y+Bullets[bullet].y, MAP_X+Bullets[bullet].x, "^");
		}
	}

}

// 画对手
void draw_enemies(void)
{
	for(int enemy=0; enemy<Enemies_len; enemy++)
	{
		if(on_map(0, Enemies[enemy].y))
		{
			mvprintw(MAP_Y+Enemies[enemy].y, MAP_X+Enemies[enemy].x, "V");
		}
	}

}



int main(void)
{
    starting();
    
    char input;
    
    struct Pos player_pos, player_last_pos;
    player_pos.x = MAP_WIDTH/2-1;
    player_last_pos.x = player_pos.x;
    player_pos.y = MAP_HEIGHT-2;
    player_last_pos.y = player_pos.y;
    
    Bullets_full=0;
    
    // 游戏主循环
    while(1)
    {
    	// Inputs
    	input = getch();
    	if(input=='q')
    	{
    		break;
    	}
    	
    	if(input=='a' && on_map(player_pos.x-2, 0) && Energy>=1)
    	{
    		player_last_pos.x = player_pos.x;
    		player_pos.x-=2;
    		Energy--;
    	}
    	if(input=='d' && on_map(player_pos.x+2+3, 0) && Energy>=1)
    	{
    		player_last_pos.x = player_pos.x;
    		player_pos.x+=2;
    		Energy--;
    	}
    	if(input=='j' && Energy>=5)
    	{
    		new_bullet(player_pos);
    		Energy-=5;
    	}
    	
    	// Draw
    	clear();
    	draw_map();
    	
    	mvprintw(MAP_Y+MAP_HEIGHT+2, MAP_X-1, "Hp: %d", Hp);
    	mvprintw(MAP_Y+MAP_HEIGHT+2, MAP_X+MAP_WIDTH-20, "Score: %d", Score);
    	mvprintw(MAP_Y+MAP_HEIGHT+3, MAP_X-1, "Energy: %d", Energy);
    	draw_player(player_pos, player_last_pos);
    	draw_bullets();
    	draw_enemies();
    	
    	// Update
    	move_bullets();
    	if(Timer%20==0)	new_enemies(1);
    	if(Timer%10==0)	move_enemies();
    	judge_hits(player_pos);
    	if(Hp<1) ending();
    	
    	// Tests
    	mvprintw(1, MAP_X+MAP_WIDTH+2, "Screen WIDTH, HEIGHT: %d, %d", WIDTH, HEIGHT);
    	mvprintw(2, MAP_X+MAP_WIDTH+2, "MAP X,Y: %d, %d", MAP_X, MAP_Y);
    	mvprintw(3, MAP_X+MAP_WIDTH+2, "MAP WIDTH, HEIGHT: %d, %d", MAP_WIDTH, MAP_HEIGHT);
    	mvprintw(4, MAP_X+MAP_WIDTH+2, "Bullets Len, Full: %d, %d", Bullets_len, Bullets_full);
    	mvprintw(5, MAP_X+MAP_WIDTH+2, "Enemies Len, Full: %d, %d", Enemies_len, Enemies_full);
    	mvprintw(7, MAP_X+MAP_WIDTH+2, "Player Pos: %d, %d", player_pos.x, player_pos.y);
    	mvprintw(8, MAP_X+MAP_WIDTH+2, "Timer: %d", Timer);
    	
    	mvprintw(1, 1, "Bullets:");
    	for(int index=0; index<Bullets_len; index++)
    	{
    		mvprintw(2+index/3, 2+15*(index%3), "%d: %d, %d", index, Bullets[index].x, Bullets[index].y);
    	}
    	
    	mvprintw(Bullets_len/3+3, 1, "Enemies:");
    	for(int index=0; index<Enemies_len; index++)
    	{
    		mvprintw(Bullets_len/3+4+index/3, 2+15*(index%3), "%d: %d, %d", index, Enemies[index].x, Enemies[index].y);
    	}
    	
    	// Time
    	refresh();
    	Timer++;
    	usleep(15000);
    	
	}
	
    ending();
    return 0;
}

 

有关C语言跨平台游戏开发的更多相关文章

  1. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  2. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  3. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  4. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  5. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  6. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  7. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  8. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  9. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  10. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

随机推荐