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


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


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

游戏框架与概念

游戏框架

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

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

//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();

定时

游戏中,有些状态通常要持续一段时间,对于这些与时间有关的实现都与定时器有关

无敌

我们希望玩家受击后进入无敌状态并且持续一段时间。

player::update(Δt){
	if(isHit){
		isInvulnerable = true;				//进入无敌状态
		timerInvulnerable.reStart();		//重启定时器
	}
	....
	
	//处于无敌状态中
	if(isInvulnerable)						//开始计时
		timerInvulnerable.timing(Δt);
}

cd

我们希望玩家攻击后进入cd状态并且持续一段时间。

player::update(Δt){
	if(isAttacked){
		isInCd = true;						//进入CD状态
		timerCd.reStart();					//重启定时器
	}
	....
	
	//处于CD状态中
	if(isInCd)								//开始计时
		timerCd.timing(Δt);
}

跳跃&落地动画

我们希望动画播放一段时间后不能再播放。以跳跃为例。

player::update(Δt){
	if(isCanPlay)
		timerCd.reStart();					//重启定时器
	
	....
	
	//处于播放状态中
	if(isPlaying)							//开始计时
		timerCd.timing(Δt);
}

物理模拟

二维就如同初中的物理题一样,仅是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;
	}	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值