🫥
0から作るゲーム開発の工夫したところ
僕はGLFW、GLAD、GLM、OpenGLを使ってMinecraftのようなゲームを作りました。その中で特に苦労したのが 物理判定と移動 です。
たとえば、キーボード入力で「W」キーを押したとします。この入力に応じて、プレイヤーの位置を単純に「x座標を-10」するような処理をしてしまうと問題が発生します。なぜなら、この方法ではプレイヤーがブロックに「めり込んだ」場合、そのめり込みがどの方向から起きたのかを判別できないからです。つまり、上から落ちてきてめり込んだのか、それとも横や下からなのかがわからなくなります。
この問題を解決するために、以下のような方法を取りました:
1. 各フレームの最後に、プレイヤーの位置を検証する処理を行います。
2. プレイヤーの位置を更新する際、forループなどを使って位置を少しずつ動かします。
3. プレイヤーが特定の方向に「めり込んだ」場合、その方向に補正を加えることで、跳ね返るような挙動を作りました(たとえば、x方向にめり込んだら、xを減少させた分だけ補正する)。
こうすることで、めり込みを防ぎ、プレイヤーが自然に動けるようにしました。
実際のコードの一部
#ifndef LINK_HPP
#define LINK_HPP
#include <cstdlib>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <vector>
#include <cmath>
#include <fstream>
#include <sstream>
#include "imgui.h" // Dear ImGui本体
#include "backends/imgui_impl_glfw.h" // GLFWバックエンド
#include "backends/imgui_impl_opengl3.h" // OpenGLバックエンド
//変数
const unsigned int WINDOW_WIDTH = 1440;
const unsigned int WINDOW_HEIGHT = 810;
glm::vec3 cameraPosition(0.0f, 0.0f, 3.0f);
glm::vec3 cameraFront(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp(0.0f, 1.0f, 0.0f);
float lastX = WINDOW_WIDTH / 2.0f;
float lastY = WINDOW_HEIGHT / 2.0f;
float yaw = -90.0f, pitch = 0.0f;
bool firstMouse = true;
float deltaTime = 0.0f, lastFrame = 0.0f;
float moveSpeed = 5.0f;
float sensitivity = 0.05f;
glm::vec3 playerPosition(100.0f, 30.0f, 250.0f); // 初期プレイヤー位置
glm::vec3 playerVelocity(0.0f, 0.0f, 0.0f); // 初期プレイヤー速度
const float gravity = -12.0f; // 重力加速度;
const float jumpStrength = 6.0f;
const float playerHeight = 1.8f; // プレイヤーの高さ
const float playerWidth = 0.6f; // プレイヤーの幅
bool onGround = false;
struct CubeData {
glm::vec3 position;
glm::vec3 color;
bool drawWireframe;
bool isSolid;
};
class CubeWorld {
public:
CubeWorld(const std::vector<CubeData>& initialCubes = {}) {
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
return;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Cube World", nullptr, nullptr);
if (window == nullptr) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return;
}
glfwMakeContextCurrent(window);
glfwSetWindowUserPointer(window, this);
glfwSetKeyCallback(window, keyHandlerWrapper);
glfwSetMouseButtonCallback(window, mouseButtonCallbackWrapper);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cerr << "Failed to initialize GLAD" << std::endl;
return;
}
glEnable(GL_DEPTH_TEST);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwSetCursorPosCallback(window, mouseCallback);
shaderProgram = createShaderProgram();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
viewLoc = glGetUniformLocation(shaderProgram, "view");
projectionLoc = glGetUniformLocation(shaderProgram, "projection");
modelLoc = glGetUniformLocation(shaderProgram, "model");
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
ImGui_ImplGlfw_InitForOpenGL(window, true);
if (window == nullptr) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return;
}
ImGui_ImplOpenGL3_Init("#version 330 core");
std::cout << "Display Size: " << io.DisplaySize.x << ", " << io.DisplaySize.y << std::endl;
glClearColor(0.53f, 0.81f, 0.92f, 1.0f);
for (const CubeData& cube : initialCubes) {
cubes.push_back({cube.position, cube.color, false, true});
}
}
~CubeWorld() {
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glDeleteProgram(shaderProgram);
glfwDestroyWindow(window);
glfwTerminate();
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
}
void AddCube(const glm::vec3& position, const glm::vec3& color, bool drawWireframe) {
cubes.push_back({position, color, drawWireframe, true});
std::cout << "Added cube at: (" << position.x << ", " << position.y << ", " << position.z << ")" << std::endl;
}
int jumpSpeed = 4;
void Update(float deltaTime) {
// カメラの前方向ベクトル(Y成分は無視して平面方向で計算)
glm::vec3 forwardDirection = glm::normalize(glm::vec3(cameraFront.x, 0.0f, cameraFront.z)); // Y成分を無視
glm::vec3 rightDirection = glm::normalize(glm::cross(forwardDirection, cameraUp)); // 右方向ベクトル
// プレイヤーの移動処理:WASDによる入力
glm::vec3 desiredVelocity(0.0f);
float run = 1.5f;
// WASDによる移動入力処理
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
if (glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS) {
desiredVelocity += forwardDirection * moveSpeed * run;
} else {
desiredVelocity += forwardDirection * moveSpeed;
}
}
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
if (glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS) {
desiredVelocity -= forwardDirection * moveSpeed * run;
} else {
desiredVelocity -= forwardDirection * moveSpeed;
}
}
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
if (glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS) {
desiredVelocity -= rightDirection * moveSpeed * run;
} else {
desiredVelocity -= rightDirection * moveSpeed;
}
}
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS){
if (glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS) {
desiredVelocity += rightDirection * moveSpeed * run;
} else {
desiredVelocity += rightDirection * moveSpeed;
}
}
// 水平移動処理:XとZ軸方向の移動
glm::vec3 movement = desiredVelocity * deltaTime;
// プレイヤーの位置更新(移動)
glm::vec3 newPosition = playerPosition + movement;
// 衝突チェック(水平移動)
if (!checkHorizontalCollision(newPosition)) {
playerPosition = newPosition; // 衝突しなければ位置を更新
}
// 垂直方向の移動(重力適用とジャンプ)
if (!onGround) {
playerVelocity.y += gravity * deltaTime; // 重力を加算
}
// 垂直移動処理
newPosition = playerPosition + glm::vec3(0.0f, playerVelocity.y * deltaTime, 0.0f);
// 垂直移動の衝突チェック
if (!checkVerticalCollision(newPosition)) {
playerPosition = newPosition; // 衝突しなければ位置を更新
} else {
// 衝突した場合、速度をゼロにして地面に着地
playerVelocity.y = 0.0f;
onGround = true; // 着地したので地面にいる
}
// 水平移動後に地面チェック
if (checkGroundStatus(playerPosition)) {
onGround = true;
} else {
onGround = false;
}
// プレイヤーの目標位置を少し上にオフセット(カメラ位置)
glm::vec3 targetCameraPosition = playerPosition + glm::vec3(0.0f, 1.5f, 0.0f);
// 補間を使ってカメラを滑らかに動かす
float smoothFactor = 0.7f; // 0.0f~1.0fでスムーズさを調整
cameraPosition = glm::mix(cameraPosition, targetCameraPosition, smoothFactor);
// 最終的なカメラ位置を更新
}
bool checkGroundStatus(const glm::vec3& position) {
// 地面との衝突判定を行い、地面にいるかどうかをチェック
// ここでは簡単にY成分をチェックする例を示す
glm::vec3 checkPosition = position - glm::vec3(0.0f, 0.1f, 0.0f); // 少し下の位置をチェック
if (checkVerticalCollision(checkPosition)) {
return true; // 地面にいる
}
return false; // 空中にいる
}
// レイとキューブの交差をデバッグ表示する関数
void debugRayIntersection(const glm::vec3& rayOrigin, const glm::vec3& rayDir, const CubeData& cube) {
float tMin = 0.0f;
if (rayIntersectsCube(rayOrigin, rayDir, cube, tMin)) {
glm::vec3 hitPosition = rayOrigin + rayDir * tMin;
}
}
void renderCrosshair(float size, ImVec4 color) {
// ウィンドウサイズの中央を取得
ImGuiIO& io = ImGui::GetIO();
float centerX = io.DisplaySize.x / 2.0f;
float centerY = io.DisplaySize.y / 2.0f;
// クロスヘアの描画
ImDrawList* drawList = ImGui::GetForegroundDrawList();
drawList->AddLine(ImVec2(centerX - size, centerY), ImVec2(centerX + size, centerY), ImGui::ColorConvertFloat4ToU32(color), 1.0f);
drawList->AddLine(ImVec2(centerX, centerY - size), ImVec2(centerX, centerY + size), ImGui::ColorConvertFloat4ToU32(color), 1.0f);
}
// 変更されたImGuiのデバッグ情報表示部分
void Render() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 view = glm::lookAt(cameraPosition, cameraPosition + cameraFront, cameraUp);
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)WINDOW_WIDTH / (float)WINDOW_HEIGHT, 0.1f, 100.0f);
glUseProgram(shaderProgram);
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
for (const CubeData& cube : cubes) {
// カメラとキューブの距離を計算
float distance = glm::length(cameraPosition - cube.position);
// 距離が50.0f以内のキューブだけ描画
if (distance <= 50.0f) {
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, cube.position);
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
// 色をシェーダーに送信
glUniform3f(glGetUniformLocation(shaderProgram, "objectColor"), cube.color.r, cube.color.g, cube.color.b);
glBindVertexArray(VAO);
glPolygonMode(GL_FRONT_AND_BACK, cube.drawWireframe ? GL_LINE : GL_FILL);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
}
}
}
bool checkVerticalCollision(const glm::vec3& position) {
for (const CubeData& cube : cubes) {
if (!cube.isSolid) continue;
// プレイヤーからの距離をチェック(5.0f以内)
float distance = glm::distance(position, cube.position);
if (distance > 5.0f) continue;
// キューブのAABB(境界ボックス)
glm::vec3 cubeMin = cube.position - glm::vec3(0.5f);
glm::vec3 cubeMax = cube.position + glm::vec3(0.5f);
// プレイヤーのAABB(縦長の立方体、少し小さめ)
glm::vec3 playerMin = position - glm::vec3(playerWidth / 2.0f - 0.15f, playerHeight / 2.0f - 0.15f, playerWidth / 2.0f - 0.15f);
glm::vec3 playerMax = position + glm::vec3(playerWidth / 2.0f - 0.15f, playerHeight / 2.0f - 0.15f, playerWidth / 2.0f - 0.15f);
// AABBの衝突判定(X, Y, Z軸で重なっているかチェック)
if (playerMax.x > cubeMin.x && playerMin.x < cubeMax.x &&
playerMax.y > cubeMin.y && playerMin.y < cubeMax.y &&
playerMax.z > cubeMin.z && playerMin.z < cubeMax.z) {
return true; // 衝突発生
}
}
return false; // 衝突なし
}
bool checkHorizontalCollision(const glm::vec3& position) {
for (const CubeData& cube : cubes) {
if (!cube.isSolid) continue;
// プレイヤーからの距離をチェック(5.0f以内)
float distance = glm::distance(position, cube.position);
if (distance > 5.0f) continue;
// キューブのAABB(境界ボックス)
glm::vec3 cubeMin = cube.position - glm::vec3(0.5f);
glm::vec3 cubeMax = cube.position + glm::vec3(0.5f);
// プレイヤーのAABB(縦長の立方体、少し小さめ)
glm::vec3 playerMin = position - glm::vec3(playerWidth / 2.0f - 0.15f, playerHeight / 2.0f - 0.15f, playerWidth / 2.0f - 0.15f);
glm::vec3 playerMax = position + glm::vec3(playerWidth / 2.0f - 0.15f, playerHeight / 2.0f - 0.15f, playerWidth / 2.0f - 0.15f);
// AABBの衝突判定(X, Y, Z軸で重なっているかチェック)
if (playerMax.x > cubeMin.x && playerMin.x < cubeMax.x &&
playerMax.y > cubeMin.y && playerMin.y < cubeMax.y &&
playerMax.z > cubeMin.z && playerMin.z < cubeMax.z) {
return true; // 衝突発生
}
}
return false; // 衝突なし
}
GLFWwindow* getWindow() const { return window; }
static void keyHandlerWrapper(GLFWwindow* window, int key, int scancode, int action, int mods) {
CubeWorld* world = reinterpret_cast<CubeWorld*>(glfwGetWindowUserPointer(window));
if (world) {
world->keyHandler(window, key, scancode, action, mods);
}
}
void keyHandler(GLFWwindow* window, int key, int scancode, int action, int mods) {
float cameraSpeed = moveSpeed * deltaTime * 2;
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
glm::vec3 oldPosition = playerPosition;
if (key == GLFW_KEY_E)
exit(0);
if (key == GLFW_KEY_SPACE && onGround) {
playerVelocity.y = jumpStrength;
onGround = false;
}
if (checkHorizontalCollision(playerPosition)) {
playerPosition = oldPosition; // 衝突が発生した場合は元の位置に戻す
}
}
}
static void mouseButtonCallbackWrapper(GLFWwindow* window, int button, int action, int mods) {
CubeWorld* world = reinterpret_cast<CubeWorld*>(glfwGetWindowUserPointer(window));
if (world) {
world->mouseButtonCallback(window, button, action, mods);
}
}
void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
glm::vec3 rayOrigin = cameraPosition;
glm::vec3 rayDirection = glm::normalize(cameraFront);
float maxDistance = 10.0f; // レイの最大距離
glm::vec3 hitPosition;
bool hit = false;
float tMinClosest = maxDistance;
int hitIndex = -1; // 交差したキューブのインデックス
// すべてのキューブに対して交差判定
for (size_t i = 0; i < cubes.size(); ++i) {
float tMin;
if (rayIntersectsCube(rayOrigin, rayDirection, cubes[i], tMin)) {
if (tMin < tMinClosest && tMin >= 0) {
tMinClosest = tMin;
hitPosition = rayOrigin + rayDirection * tMin;
hit = true;
hitIndex = i; // 交差したキューブのインデックスを記録
}
}
}
// 交差が検出された場合
if (hit && hitIndex != -1) {
// 交差したキューブを削除
std::cout << "Destroyed cube at: ("
<< cubes[hitIndex].position.x << ", "
<< cubes[hitIndex].position.y << ", "
<< cubes[hitIndex].position.z << ")" << std::endl;
cubes.erase(cubes.begin() + hitIndex); // キューブを削除
onGround = false;
}
}
if (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS) {
glm::vec3 rayOrigin = cameraPosition;
glm::vec3 rayDirection = glm::normalize(cameraFront);
float maxDistance = 10.0f;
glm::vec3 hitPosition;
bool hit = false;
float tMinClosest = maxDistance;
for (const CubeData& cube : cubes) {
float tMin;
if (rayIntersectsCube(rayOrigin, rayDirection, cube, tMin)) {
if (tMin < tMinClosest && tMin >= 0) {
tMinClosest = tMin;
hitPosition = rayOrigin + rayDirection * tMin;
hit = true;
}
}
}
if (hit) {
glm::vec3 placePosition = hitPosition - rayDirection * 0.5f;
placePosition.x = round(placePosition.x);
placePosition.y = round(placePosition.y);
placePosition.z = round(placePosition.z);
AddCube(placePosition, glm::vec3(255.0f, 0.0f, 0.0f), false);
} else {
std::cout << "No block detected within max distance." << std::endl;
}
}
}
private:
GLFWwindow* window;
unsigned int shaderProgram;
unsigned int VAO, VBO, EBO;
int viewLoc, projectionLoc, modelLoc;
std::vector<CubeData> cubes;
float vertices[216] = {
// 頂点データ: 頂点座標と色
-0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f
};
unsigned int indices[36] = {
0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4,
0, 4, 7, 7, 3, 0, 1, 5, 6, 6, 2, 1,
3, 2, 6, 6, 7, 3, 0, 1, 5, 5, 4, 0
};
std::string loadShaderSource(const char* filepath) {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "ERROR::SHADER::FILE_NOT_READABLE: " << filepath << std::endl;
return "";
}
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
unsigned int compileShader(unsigned int type, const char* source) {
unsigned int shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
int success;
char infoLog[512];
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
std::cerr << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << std::endl;
}
return shader;
}
unsigned int createShaderProgram() {
// シェーダーソースをファイルから読み込む
std::string vertexShaderSource = loadShaderSource("../Shader/vertex.glsl");
std::string fragmentShaderSource = loadShaderSource("../Shader/fragment.glsl");
unsigned int vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource.c_str());
unsigned int fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource.c_str());
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
int success;
char infoLog[512];
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
std::cerr << "ERROR::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return shaderProgram;
}
static void mouseCallback(GLFWwindow* window, double xpos, double ypos) {
static float lastX = WINDOW_WIDTH / 2.0f;
static float lastY = WINDOW_HEIGHT / 2.0f;
static bool firstMouse = true;
if (firstMouse) {
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xOffset = xpos - lastX;
float yOffset = lastY - ypos; // Y座標は上下逆
lastX = xpos;
lastY = ypos;
xOffset *= sensitivity;
yOffset *= sensitivity;
yaw += xOffset;
pitch += yOffset;
if (pitch > 89.0f) pitch = 89.0f;
if (pitch < -89.0f) pitch = -89.0f;
glm::vec3 front;
front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
front.y = sin(glm::radians(pitch));
front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
cameraFront = glm::normalize(front);
}
bool rayIntersectsCube(const glm::vec3& rayOrigin, const glm::vec3& rayDir, const CubeData& cube, float& tMin) {
glm::vec3 cubeMin = cube.position - glm::vec3(0.5f);
glm::vec3 cubeMax = cube.position + glm::vec3(0.5f);
float t1 = (cubeMin.x - rayOrigin.x) / rayDir.x;
float t2 = (cubeMax.x - rayOrigin.x) / rayDir.x;
float t3 = (cubeMin.y - rayOrigin.y) / rayDir.y;
float t4 = (cubeMax.y - rayOrigin.y) / rayDir.y;
float t5 = (cubeMin.z - rayOrigin.z) / rayDir.z;
float t6 = (cubeMax.z - rayOrigin.z) / rayDir.z;
float tmin = std::fmax(std::fmax(std::fmin(t1, t2), std::fmin(t3, t4)), std::fmin(t5, t6));
float tmax = std::fmin(std::fmin(std::fmax(t1, t2), std::fmax(t3, t4)), std::fmax(t5, t6));
if (tmax < 0 || tmin > tmax) {
return false; // No intersection
}
tMin = tmin;
return true; // Intersection detected
}
bool isPointIntersectingWithScene(const glm::vec3& point) {
float tMin;
for (const CubeData& cube : cubes) {
if (rayIntersectsCube(point, glm::vec3(0.0f, -1.0f, 0.0f), cube, tMin)) {
if (tMin <= playerHeight / 2.0f) {
return true;
}
}
}
return false;
}
};
#endif
Discussion