当前位置: 首页 > article >正文

植物明星大乱斗——功能拆解


能帮到你的话,就给个赞吧 😘


本文不做组件实现,只在组件基础上做 功能拆解以及框架实现

文章目录

  • 游戏框架与概念
    • 游戏框架
    • 单帧
  • 场景&场景管理器
  • 摄像机
    • 画面震动
  • 定时相关
    • 攻击cd
    • 无敌
    • 起跳&落地动画
  • 物理模拟
    • 重力
      • 玩家
      • 抛物线
    • 碰撞检测
      • 点与矩形
      • 矩形与矩形
  • 粒子特效
    • 粒子
    • 粒子产生器

游戏框架与概念

游戏框架

直接定位游戏过于抽象,我们先来定位于具体的一帧。

一帧是什么?带你清晰下
= 数据 + 画面
你所看到的每一帧背后都是数据,而画面则是基于数据渲染而来。
也就是 在代码层面,更新数据 + 渲染画面。即为一帧。

//t: 时间,要体现数据随时间变化
oneFrame = update(t) + render();

在此基础上,加上循环,便可实现 连续播放。

while
	oneFrame = update(t) + render();

没错,一句话加上循环便是游戏的框架。代码化为

while (1) {
//数据
	update(t);
//画面
	render();
}

在此之上加上固定60帧。便实现流畅的游戏了

#define FPS 1000/60

while (1) {
	auto frameBeginTime = now();

//数据
	update(t);

//画面
	render();

	auto frameTime = now() - frameBeginTime;

	if (frameTime < FPS)
		sleep(FPS - frameTime);
}

单帧

似乎你已经非常清晰游戏的逻辑了,但其实依然模糊,因为你非常连续,而不离散。因为你依然可以拆分。
一秒是什么?
= 60帧。化为60点,[t0, t59],这便是一秒。 在这一秒内,我们要渲染[t0, t59] 这60个画面。
也就是任意时刻的一帧t0,经过一段时间后要等于 t1。即 t0 + Δt = t1。
即 t0 + update(Δt) = t1;
所以,游戏里 Δt和 帧 便组成了游戏。这也是为什么update(t)的参数需要有个t。还不严谨,应该是Δt。所以正确的代码应该是

#define FPS 1000/60

while (1) {
	auto frameBeginTime = now();

//数据
	update(Δt);

//画面
	render();

	auto frameTime = now() - frameBeginTime;

	if (frameTime < FPS)
		sleep(FPS - frameTime);
}

任意一帧进入循环时还是t0,经过update(Δt)后,便是t1的数据了。

至此,你才理解游戏的框架与帧与时间。

场景&场景管理器

面向对象中,单写update和render是不行的,而是要写清楚对象。
身为第九艺术,场景自然是其对象。

#define FPS 1000/60

while (1) {
	auto frameBeginTime = now();

//数据
	scene.update(Δt);

//画面
	scene.render();

	auto frameTime = now() - frameBeginTime;

	if (frameTime < FPS)
		sleep(FPS - frameTime);
}

自然的,在众多场景中,需要一个场景管理器统一管理,亦如导演一样。即

#define FPS 1000/60

while (1) {
	auto frameBeginTime = now();

//数据
	sceneManager.update(Δt);

//画面
	sceneManager.render();

	auto frameTime = now() - frameBeginTime;

	if (frameTime < FPS)
		sleep(FPS - frameTime);
}

场景管理器最重要的功能莫过于切换。

public:
	void switchTo(Scene* scene);	//切换到scene

但是切换这个功能又由谁来调用呢,没错,场景。
所以这是个交叉引用,即场景管理器需要调用场景,而场景又需要调用场景管理器。
交叉引用实现有很多,但本文意不在此,仅注重功能分析。
那么接下来,调用场景管理器切换,如在gameScene的update中

void GameScene::update(int Δt){
	//玩家生命值0时,切换到菜单场景。
	if(player->getHp() == 0)
		sceneManager.switchTo(menuScene);
}

但此时,游戏场景内还需要存有菜单场景的引用,这就牵扯到场景内互相引用了,我们不希望场景之间互相引用,因为会非常混乱。所以,场景管理器的声明应如下

public:
	void switchTo(SceneType sceneType);		//切换到scene

使用枚举类来取代直接引用。

摄像机

如同场景一样,我们希望由摄像机来显示画面,而不是直接显示。
如显示玩家

void render(){
	//直接显示玩家
	putimage(player.x, player.y);
}
void render(){
	//摄像机间接显示玩家
	putimage(player.x - camera.x, player.y - camera.y);
}

这样,当移动摄像机时,player的画面也会随之移动。

void render(){
	camera.moveUp(1);	//摄像机向上移动1单位,player画面也会向上移动1单位。
	putimage(player.x - camera.x, player.y - camera.y);
}

画面震动

那么,当所有画面都有摄像机参与时,实现震动便简单多了

camera.shake();	
render();

定时相关

下面这些功能都与定时相关。
如攻击cd,无敌时间,起跳&落地动画。游戏中还有许多功能与定时有关,暂不列举。

攻击cd

我们希望玩家攻击后不能再攻击,cd过后再攻击。

if(isCanAttack){
	//攻击
	attack();
	
	//攻击后不能再攻击
	isCanAttack = false;
	
	//开始计时
	timer.timer(Δt);
}
timer.setCallBack(isCanAttack = true);

无敌

我们希望玩家受击后不能再受击,cd过后再受击。

if(isCanBeHit){
	//受击
	beHit();
	
	//受击后不能再受击
	isCanBeHit= false;
	
	//开始计时
	timer.timer(Δt);
}
timer.setCallBack(isCanBeHit = true);

起跳&落地动画

我们希望动画播放一段时间后不能再播放。以起跳为例。
注,之前是cd过后希望再重复,而动画则是不再重复。

if(isCanPlay){
	//播放
	jumpPlay();
	
	//开始计时
	timer.timer(Δt);
}
timer.setCallBack(isCanPlay= false);

物理模拟

二维就如同初中的物理题一样,仅是xy方向上的位移,速度,与 时间 而已。

Vector2 position, velocity;		//位移,速度
Δt;								//时间

重力

#define g -9.8

玩家

void Player::update(Δt){
	//玩家速度
		//添加重力速度即可模拟重力
	velocity.y += g * Δt;
	//玩家位移
	position += velocity * Δt;
}

抛物线

//抛物线初始速度
velocity.x = x, velocity.y = x;

void sunBullet::update(Δt){
	//速度
	velocity.y += g * Δt;
	//位移
	position += velocity * Δt;
}

碰撞检测

游戏中仅用到两种碰撞,二维应该也就这两种碰撞吧。

rectangle{
	x1,y1;	//左上角
	x2,y2;	//右下角
	width;
	height;
}

点与矩形

bool checkCollision(rectangle target){
	//仅需检测x 是否在 target内,y是否在target内
	auto collisionX = x >= target.x1 && x <= target.x2;
	auto collisionY = y >= target.y1 && y <= target.y2;
	
	return collisionX && collisionY;
}

矩形与矩形

bool checkCollision(rectangle target){
	//仅需检测两个矩形xy是否相交即可
	auto collisionX = max(x2, target.x2) - min(x1, target.x1) <= width + target.width;
	auto collisionY = max(y2, target.y2) - min(y1, target.y1) <= height + target.height;
	
	return collisionX && collisionY;
}

粒子特效

粒子特效由两部分组成
  粒子
  粒子产生器
粒子是一个有生命期的对象,过完生命期便消失
粒子产生器则负责产生粒子

class Player{
private:
	particles;						//粒子
	particleGeneration;				//粒子生成器
private:
	void run(Δt){
		//跑动
		...
		
		//跑动时,生成粒子					
		particleGeneration.generation(Δt);
	}
public:
	void update(Δt){
		//玩家按下跑动键-生成粒子
		if(isRun)
			run(Δt);
		
		//更新粒子
			//粒子生成后便需一直更新
		for (auto& particle : particles)
			particle.update(Δt);
			
		//移除失效粒子
		particles.erase(std::remove_if(particles.begin(), particles.end(), [](const Particle& particle) {
			return !particle.checkValid();
		}), particles.end());
	}
}				

粒子

void Particle::update(Δt){
	time += Δt;
	
	//大于生命期则失效
	if (time > lifeSpan){
		isValid = false;
		return ;
	}	
}
bool Particle::checkValid() const{
	return isValid;
}

粒子产生器

void Generation::generation(Δt){
	//Δt内一直生成粒子
	while(Δt > 0){		
		particles.push_back(Particle());
		Δt -= interval;
	}	
}

http://www.kler.cn/a/409534.html

相关文章:

  • 内存不足引发C++程序闪退崩溃问题的分析与总结
  • 前端:JavaScript (学习笔记)【2】
  • Java 8 Stream API 在数据转换中的应用 —— 将列表转换为映射
  • (Keil)MDK-ARM各种优化选项详细说明、实际应用及拓展内容
  • 快速获取镜像包的方法
  • 动态反馈控制器(DFC)和 服务率控制器(SRC);服务率和到达率简单理解
  • ffmpeg.wasm 在浏览器运行ffmpeg操作视频
  • day27|leetCode 455. 分发饼干,376.摆动序列,53. 最大子数组和
  • 快速排序算法-C语言
  • GitLab 使用过程中常见问题及解决方案
  • 【人工智能】Transformers之Pipeline(二十五):图片特征抽取(image-feature-extraction)
  • Vue.js 学习总结(14)—— Vue3 为什么推荐使用 ref 而不是 reactive
  • c ++零基础可视化——字符串
  • 用js实现点击抽奖
  • C# 读取多条数据记录导出到 Word标签模板之图片输出改造
  • 图解GitLab DevSecOps 流程
  • LabVIEW发动机热磨合试验台
  • 基于Angular+BootStrap+SpringBoot简单的购物网站
  • 实现在两台宿主机下的docker container 中实现多机器通讯
  • 【前端】JavaScript 变量声明与函数提升例题分析:深入理解变量提升、作用域链与函数调用
  • meterpreter常用命令 下
  • 取石子游戏
  • L1G1000 书生大模型全链路开源开放体系笔记
  • Python中使用matplotlib绘制图像并填充满整个figure区域
  • iphone小程序设置burpsuite代理抓包
  • 深入FastAPI:表单和文件上传详解