Hỏi về việc làm delay vòng lặp trong lập trình game

Hiện em đang làm game với SDL2 , và đang mắc ở 1 chỗ mong mọi người giải đáp giùm em .


Ý tưởng game : hứng các đồ vật rơi từ trên xuống -> Em có 1 Threatobject biểu diễn cho 1 item rơi từ trên xuống , sau đó e khởi tạo 1 vector<ThreatObject * > để lưu các ThreatObject khác nhau , và ở trong hàm main em có dùng vòng lặp for để chạy các ThreatObject , nhưng do tgian chạy vòng lặp quá nhanh nên các item nó " RƠI XUỐNG CÙNG 1 LÚC "

Và em muốn fix lại sao cho các item nó rơi lẻ tẻ , không bị cùng 1 lúc

Và đang không biết xử lý ntn ạ


Đây là các hàm :
1 . ThreatObject.h

#pragma once
#ifndef THREAT_OBJECT_H_
#define THREAT_OBJECT_H_

#include "CommonFunction.h"
#include "BaseObject.h"

#define THREAT_FRAME_NUM 10
#define THREAT_FALL_SPEED 4

const int threat_pos_val[10] = { 0,50,100,150,200,250,300,350,400,450 };

class ThreatObject : public BaseObject
{
public:
	ThreatObject();
	~ThreatObject();

	bool LoadImage(std::string path, SDL_Renderer* screen);
	void Show(SDL_Renderer* des);
	void SetClips();
	void ThreatsFall();

private:
	int frame_;
	int width_frame_;
	int height_frame_;
	SDL_Rect frame_clip_[THREAT_FRAME_NUM];
	
	float x_pos_;
	float y_pos_;

};
#endif // !1

  1. ThreatObject.cpp
#include "pch.h"
#include "ThreatObject.h"


ThreatObject::ThreatObject()
{

	 frame_ = 0 + rand() % (9 + 1 - 0); 
	 width_frame_=0;
	 height_frame_ = 0;
	 x_pos_ = threat_pos_val[frame_];
	 y_pos_ = 0;

}
ThreatObject::~ThreatObject()
{

}

bool ThreatObject::LoadImage(std::string path, SDL_Renderer* screen)
{
	bool ret = BaseObject::LoadImage(path, screen);
	if (ret)
	{
		width_frame_ = rect_.w / THREAT_FRAME_NUM;
		height_frame_ = rect_.h;
	}
	return ret;
}

void ThreatObject::SetClips()
{
	if (width_frame_ > 0 && height_frame_ > 0)
	{
		for (int i = 0; i < THREAT_FRAME_NUM; i++)
		{
			frame_clip_[i].x = i * width_frame_;
			frame_clip_[i].y = 0;
			frame_clip_[i].w = width_frame_;
			frame_clip_[i].h = height_frame_;
		}
	}
}
void ThreatObject::Show(SDL_Renderer* des)
{
	
		LoadImage("img//threatobject.png", des);
	
		//frame_ = 0 + rand() % (9 + 1 - 0);
		rect_.x = x_pos_;
		rect_.y = y_pos_;


	SDL_Rect* current_clip = &frame_clip_[frame_];

	SDL_Rect renderQuad = { rect_.x,rect_.y,width_frame_,height_frame_ };

	SDL_RenderCopy(des, p_object_, current_clip, &renderQuad);
}
void ThreatObject::ThreatsFall()
{
	y_pos_ += THREAT_FALL_SPEED;
	if (y_pos_ > SCREEN_GROUND)
	{
		frame_ = 0 + rand() % (9 + 1 - 0);
		width_frame_ = 0;
		height_frame_ = 0;
		x_pos_ = threat_pos_val[frame_];
		y_pos_ = 0;
	}
}

  1. main.cpp
// DemoStickyRun.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include "pch.h"
#include"CommonFunction.h"
#include "BaseObject.h"
#include"PlayerObject.h"
#include"SettingFps.h"
#include "ThreatObject.h"
#include "ItemObject.h"
//#include "gamemap.h"

BaseObject g_background;


bool InitData()
{
	// khởi tạo flag
	bool success = true;
	// hàm khởi tạo chế độ SDL
	int ret = SDL_Init(SDL_INIT_VIDEO);
	if (ret < 0)
		return false;
	// set kích thước chuẩn hco màn hình
	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
	// khởi tạo cửa sổ 
	g_window = SDL_CreateWindow("Sticky Run CPP SDL", SDL_WINDOWPOS_UNDEFINED,
								SDL_WINDOWPOS_UNDEFINED,
								SCREEN_WIDTH, SCREEN_HEIGHT,
								SDL_WINDOW_SHOWN);
	if (g_window == NULL) {
		success = false;
	}
	else {
		// Khởi tạo renderer cho window
		g_screen = SDL_CreateRenderer(g_window, -1, SDL_RENDERER_ACCELERATED);
		if (g_screen == NULL) {
			success = false;
		}
		else {
			// thiết lập màu sắc cho phần renderer
			SDL_SetRenderDrawColor(g_screen, RENDER_DRAW_COLOR, RENDER_DRAW_COLOR, RENDER_DRAW_COLOR, RENDER_DRAW_COLOR); 
			// khởi tạo PNG
			int imgFlags = IMG_INIT_PNG;
			if (!(IMG_Init(imgFlags) && imgFlags)) {
				success = false;
			}
		}
	}
	return success;
		 
}

bool LoadBackGround()
{
	bool ret = g_background.LoadImage("img/aaaaaaaaaaaaaaaaa.jpg", g_screen);
	if 
		
		
		
		
		
		(ret == false)
		return false;

	return true;
}

void close()
{
	g_background.Free();

	SDL_DestroyRenderer(g_screen);
	g_screen = NULL;

	SDL_DestroyWindow(g_window);
	g_window = NULL;

	IMG_Quit();
	SDL_Quit();
}

std::vector<ThreatObject*> MakeThreadList()
{
	std::vector<ThreatObject*> list_threats;

	ThreatObject* threats_object = new ThreatObject[10];

	for (int i = 0; i < 10; i++)
	{
		ThreatObject* p_threat = threats_object + i;
		if (p_threat != NULL)
		{
			p_threat->LoadImage("img//threatobject.png", g_screen);
			p_threat->SetClips();
			
			list_threats.push_back(p_threat);
		}
	}
	return list_threats;
}

int main(int argc, char* argv[])
{

	srand((int)time(0));

	SetTime fps_time;

	if (InitData() == false)
		return -1;
	if (LoadBackGround() == false)
		return -1;

	PlayerObject p_player;
	p_player.LoadImage("img//playerright.png", g_screen);
	p_player.SetClips();

	std::vector<ThreatObject*> threats_list = MakeThreadList();
	/*
	ThreatObject threat_;
	threat_.LoadImage("img//threatobject.png", g_screen);
	threat_.SetClips();
	*/
	
	//threat_.LoadImage("img//threatobject.png", g_screen);

	ItemObject item_;

	bool is_quit = false;
	while (!is_quit)
	{
		fps_time.start();
		// Xử lý sự kiện trên các hàng đợi
		while (SDL_PollEvent(&g_event) != 0)
		{
			// người chơi yêu cầu quit
			if (g_event.type == SDL_QUIT)
			{
				is_quit = true;
			}
			p_player.HandleEvent(g_event, g_screen);
			
		}
		SDL_SetRenderDrawColor(g_screen, RENDER_DRAW_COLOR, RENDER_DRAW_COLOR, RENDER_DRAW_COLOR, RENDER_DRAW_COLOR);
		// Xóa màn hình
		SDL_RenderClear(g_screen);
		// Đưa texture ra màn hình
		g_background.Render(g_screen, NULL);
		
		p_player.PlayerRun();
		p_player.Show(g_screen);
		

		for (int i = 0; i < threats_list.size(); i++)
		{
				ThreatObject* p_threat = threats_list.at(i);
				if (p_threat != NULL)
				{
					p_threat->ThreatsFall();
					p_threat->Show(g_screen);
				}

		}
		item_.LoadImage(item_object[item_.getNum_img_()], g_screen);
		item_.ItemsFall();
		item_.Show(g_screen);
		
		
		/*
		threat_.ThreatsFall();
		threat_.Show(g_screen);
		*/
		// update lại màn hình
		SDL_RenderPresent(g_screen);

		int real_one_frame = fps_time.get_ticks();
		int theory_one_frame = 1000 / FRAME_PER_SECOND;
		if (real_one_frame < theory_one_frame)
		{
			int delay_time = theory_one_frame - real_one_frame;
			SDL_Delay(delay_time);

		}
	}
	close();
	return 0;
}

Mong mọi người giúp đỡ ạ , em cảm ơn rất nhiều ạ

sửa mấy cái int lại thành float hết xem sao? 1000.f / FRAME_PER_SECOND; nữa

thử giảm THREAT_FALL_SPEED xuống 1, 2 xem nó có rơi chậm hơn ko :V

fps_time.get_ticks(); là hàm nào đây, có phải trả về số mili giây ko? hay là trả về số micro giây gì, ko thấy code của class SetTime ở đây? Nếu là micro giây thì cái if (real_one_frame < theory_one_frame) có bao giờ được gọi đâu mà có delay giữa 2 frame :V

2 Likes

Mình thấy cách tiếp cận này rất dở, khuyên bạn nên đi theo hướng data driven:

  • Bạn làm 1 file config, ghi chính xác từng Vật thể sẽ rơi sau bao nhiêu giây/mili-giây từ khi bắt đầu chơi, hoặc bạn có thể random ra 1 cái config;
  • Bạn làm 1 cái mảng/list để lưu những Vật thể nào đang được render;
  • Bạn làm 1 cái bộ đếm thời gian, cứ đến đúng thời điểm được ghi trong config thì bốc Vật thể tương ứng ra và bỏ vào mảng/list render.

Tiện mình hỏi thử, mình thấy hàm SDL_Delay để delay, như vậy là tuy delay nhưng game vẫn render bình thường, tức là SDL đặt 1 thread riêng biệt để vẽ đồ họa à.

1 Like

render xong rồi mới delay :V render có ví dụ 1ms à, ko delay ví dụ thêm 15ms nữa thì nó chạy 1000fps :V

2 Likes

ủa vậy delay thì các vật thể còn rơi tiếp không, hay là tạm dừng rơi trong giai đoạn delay?

1 Like

dừng, giống như viết vậy nè

while (true)
{
    std::cout << "hello\n";
}

thì nó in ra như vũ bão :V
còn có delay thì

while (true)
{
    std::cout << "hello\n";
    Sleep(1000);
}

thì nó in hello 1 giây 1 lần thôi thay vì 1000 lần 1 giây :V mỗi lần cout ở đây là mỗi lần vật thể “rơi” 4 pixel. 60fps, 1 frame rơi 4 px nghĩa là 240px 1 giây, màn hình 720px thì 3 giây là từ trên đỉnh rơi xuống đất, tốc độ cũng ko phải nhanh lắm.

2 Likes

Nếu thế thì minh nói thật là code này rất vô nghĩa về gameplay, đằng nào các vật thể cũng sẽ rơi cùng một lúc :roll_eyes:

1 Like

đúng rồi, mỗi vật thể nên có tốc độ rơi riêng hoặc spawn khác frame để nó rơi khác thời điểm

lệ thuộc frame kiểu này tùy game, game offline thì ok, online thì ko lệ thuộc frame kiểu này được vì máy user mỗi người nhanh chậm khác nhau, máy bình thường 60fps sẽ rơi lẹ hơn máy yếu chạy được có 30fps :V

nếu ko muốn lệ thuộc frame thì phải tính y_pos += ??? với time delta tính từ thời điểm trước, ví dụ 14ms, tốc độ 240px/s thì ??? = 240 * 0.014 = 3.36, nếu time delta là 36ms thì ??? = 240 * 0.036 = 8.64 v.v… lúc này dù có 60fps hay 30pfs thì vật thể cũng rơi đúng 240px/s. Khi này ta có thể cho user vặn lên 120-144fps tùy vào máy mạnh hay ko chứ ko cần cố định 60fps nữa :V

tốt hơn nữa thì xài fixed time delta thay vì time delta lệ thuộc vào thời gian giữa các frame :V :V :V vì physics nếu ko cố định time step thì tính toán va chạm 2 máy khác frame rate có thể ra kết quả va chạm khác nhau :V

2 Likes

Hi Dfx Nguyễn.
Việc dùng số khung hình đã qua từ lúc chạy không phải là cách hay để làm đơn vị thời gian. Bạn có thể dùng hàm lấy mili giây của SDL2 và kết hợp với công thức tính quãng đường theo vận tốc và thời gian s = v . t.
P/S Các đơn vị trong hệ thống có thể dùng m, m/s, s và ánh sạ vào tọa độ của màn hình.
https://thenumbat.github.io/cpp-course/sdl2/08/08.html

2 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?