搭建 LightGame 游戏框架
This commit is contained in:
@@ -0,0 +1,439 @@
|
||||
#include "GameObject.h"
|
||||
#include "Level.h"
|
||||
#include "Physics2D.h"
|
||||
#include "LightEffectSystem.h"
|
||||
#include "LevelLoader.h"
|
||||
#include "LevelData.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
using namespace LightGame;
|
||||
|
||||
namespace
|
||||
{
|
||||
void TestGameObjectDefaults()
|
||||
{
|
||||
GameObject obj;
|
||||
assert(obj.id == 0);
|
||||
assert(obj.type == GameObjectType::StaticPlatform);
|
||||
assert(obj.position.x == 0);
|
||||
assert(obj.position.y == 0);
|
||||
assert(obj.velocity.x == 0);
|
||||
assert(obj.velocity.y == 0);
|
||||
assert(obj.sprite == nullptr);
|
||||
assert(obj.active == true);
|
||||
assert(obj.solid == true);
|
||||
assert(obj.flip_h == false);
|
||||
}
|
||||
|
||||
void TestGameObjectWorldCollider()
|
||||
{
|
||||
GameObject obj;
|
||||
obj.position = Math::Vector2Int(100, 200);
|
||||
obj.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0),
|
||||
Math::Vector2Int(32, 32));
|
||||
|
||||
RenderData::BoundingBox2D world = obj.get_world_collider();
|
||||
assert(world.min.x == 100);
|
||||
assert(world.min.y == 200);
|
||||
assert(world.max.x == 132);
|
||||
assert(world.max.y == 232);
|
||||
}
|
||||
|
||||
void TestLevelAddAndGet()
|
||||
{
|
||||
Level level;
|
||||
assert(level.object_count() == 0);
|
||||
|
||||
GameObject platform;
|
||||
platform.type = GameObjectType::StaticPlatform;
|
||||
platform.position = Math::Vector2Int(0, 400);
|
||||
uint32_t id1 = level.add_object(platform);
|
||||
assert(id1 == 1);
|
||||
assert(level.object_count() == 1);
|
||||
|
||||
GameObject player;
|
||||
player.type = GameObjectType::Player;
|
||||
player.position = Math::Vector2Int(50, 350);
|
||||
uint32_t id2 = level.add_object(player);
|
||||
assert(id2 == 2);
|
||||
assert(level.object_count() == 2);
|
||||
|
||||
const GameObject* found = level.get_object(id1);
|
||||
assert(found != nullptr);
|
||||
assert(found->type == GameObjectType::StaticPlatform);
|
||||
assert(found->id == 1);
|
||||
|
||||
const GameObject* not_found = level.get_object(999);
|
||||
assert(not_found == nullptr);
|
||||
}
|
||||
|
||||
void TestLevelRemove()
|
||||
{
|
||||
Level level;
|
||||
GameObject obj;
|
||||
obj.type = GameObjectType::Collectible;
|
||||
uint32_t id = level.add_object(obj);
|
||||
assert(level.object_count() == 1);
|
||||
|
||||
level.remove_object(id);
|
||||
assert(level.object_count() == 0);
|
||||
assert(level.get_object(id) == nullptr);
|
||||
}
|
||||
|
||||
void TestLevelQueryByType()
|
||||
{
|
||||
Level level;
|
||||
|
||||
GameObject platform;
|
||||
platform.type = GameObjectType::StaticPlatform;
|
||||
level.add_object(platform);
|
||||
|
||||
GameObject light_plat;
|
||||
light_plat.type = GameObjectType::LightPlatform;
|
||||
level.add_object(light_plat);
|
||||
|
||||
GameObject player;
|
||||
player.type = GameObjectType::Player;
|
||||
level.add_object(player);
|
||||
|
||||
auto platforms = level.get_objects_by_type(GameObjectType::StaticPlatform);
|
||||
assert(platforms.size() == 1);
|
||||
assert(platforms[0]->type == GameObjectType::StaticPlatform);
|
||||
|
||||
auto players = level.get_objects_by_type(GameObjectType::Player);
|
||||
assert(players.size() == 1);
|
||||
|
||||
auto hazards = level.get_objects_by_type(GameObjectType::Hazard);
|
||||
assert(hazards.size() == 0);
|
||||
}
|
||||
|
||||
void TestLevelQueryRegion()
|
||||
{
|
||||
Level level;
|
||||
|
||||
GameObject obj1;
|
||||
obj1.type = GameObjectType::StaticPlatform;
|
||||
obj1.position = Math::Vector2Int(100, 100);
|
||||
obj1.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0), Math::Vector2Int(32, 32));
|
||||
level.add_object(obj1);
|
||||
|
||||
GameObject obj2;
|
||||
obj2.type = GameObjectType::StaticPlatform;
|
||||
obj2.position = Math::Vector2Int(500, 500);
|
||||
obj2.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0), Math::Vector2Int(32, 32));
|
||||
level.add_object(obj2);
|
||||
|
||||
RenderData::BoundingBox2D query(
|
||||
Math::Vector2Int(80, 80), Math::Vector2Int(200, 200));
|
||||
auto results = level.query_region(query);
|
||||
assert(results.size() == 1);
|
||||
assert(results[0]->position.x == 100);
|
||||
}
|
||||
|
||||
void TestLevelInactiveObjectsExcluded()
|
||||
{
|
||||
Level level;
|
||||
|
||||
GameObject obj;
|
||||
obj.type = GameObjectType::Collectible;
|
||||
obj.active = true;
|
||||
uint32_t id = level.add_object(obj);
|
||||
|
||||
auto active = level.get_objects_by_type(GameObjectType::Collectible);
|
||||
assert(active.size() == 1);
|
||||
|
||||
GameObject* found = level.get_object(id);
|
||||
found->active = false;
|
||||
|
||||
auto active2 = level.get_objects_by_type(GameObjectType::Collectible);
|
||||
assert(active2.size() == 0);
|
||||
}
|
||||
|
||||
void TestLevelCheckpoints()
|
||||
{
|
||||
Level level;
|
||||
level.set_spawn_point(Math::Vector2Int(50, 300));
|
||||
|
||||
Checkpoint cp1(200, 300);
|
||||
Checkpoint cp2(400, 200);
|
||||
level.add_checkpoint(cp1);
|
||||
level.add_checkpoint(cp2);
|
||||
|
||||
Math::Vector2Int respawn = level.get_active_checkpoint();
|
||||
assert(respawn.x == 50);
|
||||
assert(respawn.y == 300);
|
||||
|
||||
level.get_all_objects();
|
||||
auto& cps = level.get_checkpoints();
|
||||
const_cast<Checkpoint&>(cps[0]).activated = true;
|
||||
|
||||
respawn = level.get_active_checkpoint();
|
||||
assert(respawn.x == 200);
|
||||
assert(respawn.y == 300);
|
||||
|
||||
const_cast<Checkpoint&>(cps[1]).activated = true;
|
||||
respawn = level.get_active_checkpoint();
|
||||
assert(respawn.x == 400);
|
||||
assert(respawn.y == 200);
|
||||
}
|
||||
|
||||
void TestAabbOverlap()
|
||||
{
|
||||
RenderData::BoundingBox2D a(Math::Vector2Int(0, 0), Math::Vector2Int(10, 10));
|
||||
RenderData::BoundingBox2D b(Math::Vector2Int(5, 5), Math::Vector2Int(15, 15));
|
||||
assert(aabb_overlap(a, b) == true);
|
||||
|
||||
RenderData::BoundingBox2D c(Math::Vector2Int(20, 20), Math::Vector2Int(30, 30));
|
||||
assert(aabb_overlap(a, c) == false);
|
||||
|
||||
RenderData::BoundingBox2D d(Math::Vector2Int(10, 0), Math::Vector2Int(20, 10));
|
||||
assert(aabb_overlap(a, d) == false);
|
||||
|
||||
RenderData::BoundingBox2D e(Math::Vector2Int(9, 0), Math::Vector2Int(19, 10));
|
||||
assert(aabb_overlap(a, e) == true);
|
||||
}
|
||||
|
||||
void TestGravityAccumulation()
|
||||
{
|
||||
Physics2D physics;
|
||||
PhysicsConfig config;
|
||||
config.gravity = 1000;
|
||||
config.max_fall_speed = 500;
|
||||
physics.set_config(config);
|
||||
|
||||
GameObject obj;
|
||||
obj.velocity = Math::Vector2Int(0, 0);
|
||||
|
||||
physics.apply_gravity(obj, 16);
|
||||
assert(obj.velocity.y > 0);
|
||||
assert(obj.velocity.y <= config.max_fall_speed);
|
||||
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
physics.apply_gravity(obj, 16);
|
||||
}
|
||||
assert(obj.velocity.y == config.max_fall_speed);
|
||||
}
|
||||
|
||||
void TestGroundDetection()
|
||||
{
|
||||
Level level;
|
||||
level.set_bounds(LevelBounds(0, 0, 800, 600));
|
||||
|
||||
GameObject ground;
|
||||
ground.type = GameObjectType::StaticPlatform;
|
||||
ground.position = Math::Vector2Int(0, 400);
|
||||
ground.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0), Math::Vector2Int(800, 32));
|
||||
ground.solid = true;
|
||||
level.add_object(ground);
|
||||
|
||||
GameObject player;
|
||||
player.type = GameObjectType::Player;
|
||||
player.position = Math::Vector2Int(100, 368);
|
||||
player.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0), Math::Vector2Int(16, 32));
|
||||
|
||||
Physics2D physics;
|
||||
assert(physics.is_grounded(player, level) == true);
|
||||
|
||||
player.position = Math::Vector2Int(100, 300);
|
||||
assert(physics.is_grounded(player, level) == false);
|
||||
}
|
||||
|
||||
void TestOneWayPlatform()
|
||||
{
|
||||
Level level;
|
||||
level.set_bounds(LevelBounds(0, 0, 800, 600));
|
||||
|
||||
GameObject platform;
|
||||
platform.type = GameObjectType::LightPlatform;
|
||||
platform.position = Math::Vector2Int(100, 300);
|
||||
platform.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0), Math::Vector2Int(64, 16));
|
||||
platform.solid = true;
|
||||
level.add_object(platform);
|
||||
|
||||
GameObject player;
|
||||
player.type = GameObjectType::Player;
|
||||
player.position = Math::Vector2Int(110, 320);
|
||||
player.collider = RenderData::BoundingBox2D(
|
||||
Math::Vector2Int(0, 0), Math::Vector2Int(16, 32));
|
||||
player.velocity = Math::Vector2Int(0, -100);
|
||||
|
||||
Physics2D physics;
|
||||
physics.move_and_collide(player, level, 16);
|
||||
|
||||
assert(player.position.y < 320);
|
||||
}
|
||||
|
||||
void TestLightPlatformActivation()
|
||||
{
|
||||
LightEffectSystem system;
|
||||
|
||||
GameObject light_plat;
|
||||
light_plat.type = GameObjectType::LightPlatform;
|
||||
light_plat.light_threshold.min_level = 2000;
|
||||
light_plat.solid = false;
|
||||
|
||||
std::vector<GameObject> objects;
|
||||
objects.push_back(light_plat);
|
||||
|
||||
system.update(objects, 1000);
|
||||
assert(objects[0].solid == false);
|
||||
|
||||
system.update(objects, 2500);
|
||||
assert(objects[0].solid == true);
|
||||
|
||||
system.update(objects, 2000);
|
||||
assert(objects[0].solid == true);
|
||||
}
|
||||
|
||||
void TestShadowPlatformActivation()
|
||||
{
|
||||
LightEffectSystem system;
|
||||
|
||||
GameObject shadow_plat;
|
||||
shadow_plat.type = GameObjectType::ShadowPlatform;
|
||||
shadow_plat.light_threshold.max_level = 1000;
|
||||
shadow_plat.solid = true;
|
||||
|
||||
std::vector<GameObject> objects;
|
||||
objects.push_back(shadow_plat);
|
||||
|
||||
system.update(objects, 2000);
|
||||
assert(objects[0].solid == false);
|
||||
|
||||
system.update(objects, 500);
|
||||
assert(objects[0].solid == true);
|
||||
|
||||
system.update(objects, 1000);
|
||||
assert(objects[0].solid == true);
|
||||
}
|
||||
|
||||
void TestLightDoorOpen()
|
||||
{
|
||||
LightEffectSystem system;
|
||||
|
||||
GameObject door;
|
||||
door.type = GameObjectType::Door;
|
||||
door.light_threshold.min_level = 1500;
|
||||
door.light_threshold.max_level = 2500;
|
||||
door.solid = true;
|
||||
|
||||
std::vector<GameObject> objects;
|
||||
objects.push_back(door);
|
||||
|
||||
system.update(objects, 1000);
|
||||
assert(objects[0].solid == true);
|
||||
|
||||
system.update(objects, 2000);
|
||||
assert(objects[0].solid == false);
|
||||
|
||||
system.update(objects, 3000);
|
||||
assert(objects[0].solid == true);
|
||||
|
||||
system.update(objects, 1500);
|
||||
assert(objects[0].solid == false);
|
||||
|
||||
system.update(objects, 2500);
|
||||
assert(objects[0].solid == false);
|
||||
}
|
||||
|
||||
void TestLightEffectIgnoresOtherTypes()
|
||||
{
|
||||
LightEffectSystem system;
|
||||
|
||||
GameObject platform;
|
||||
platform.type = GameObjectType::StaticPlatform;
|
||||
platform.solid = true;
|
||||
|
||||
GameObject hazard;
|
||||
hazard.type = GameObjectType::Hazard;
|
||||
hazard.solid = true;
|
||||
|
||||
std::vector<GameObject> objects;
|
||||
objects.push_back(platform);
|
||||
objects.push_back(hazard);
|
||||
|
||||
system.update(objects, 0);
|
||||
assert(objects[0].solid == true);
|
||||
assert(objects[1].solid == true);
|
||||
|
||||
system.update(objects, 4095);
|
||||
assert(objects[0].solid == true);
|
||||
assert(objects[1].solid == true);
|
||||
}
|
||||
|
||||
void TestLevelLoaderBasic()
|
||||
{
|
||||
const uint16_t tiles[] = {
|
||||
0, 0, 0,
|
||||
0, 0, 0,
|
||||
1, 1, 1,
|
||||
};
|
||||
|
||||
const ObjectSpawn spawns[] = {
|
||||
{ GameObjectType::Player, 1, 1, 0, 0, TriggerAction::None, 0 },
|
||||
{ GameObjectType::StaticPlatform, 0, 2, 0, 0, TriggerAction::None, 0 },
|
||||
};
|
||||
|
||||
const LevelData data = {
|
||||
tiles,
|
||||
3,
|
||||
3,
|
||||
16,
|
||||
nullptr,
|
||||
0,
|
||||
spawns,
|
||||
2,
|
||||
Math::Vector2Int(16, 16),
|
||||
0,
|
||||
0,
|
||||
48,
|
||||
48
|
||||
};
|
||||
|
||||
Level level;
|
||||
LevelLoader::load(level, data);
|
||||
|
||||
assert(level.object_count() == 2);
|
||||
assert(level.get_bounds().max_x == 48);
|
||||
assert(level.get_bounds().max_y == 48);
|
||||
assert(level.get_spawn_point().x == 16);
|
||||
assert(level.get_spawn_point().y == 16);
|
||||
|
||||
const RenderData::Tilemap& tm = level.get_tilemap();
|
||||
assert(tm.width == 3);
|
||||
assert(tm.height == 3);
|
||||
assert(tm.tiles == tiles);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
TestGameObjectDefaults();
|
||||
TestGameObjectWorldCollider();
|
||||
TestLevelAddAndGet();
|
||||
TestLevelRemove();
|
||||
TestLevelQueryByType();
|
||||
TestLevelQueryRegion();
|
||||
TestLevelInactiveObjectsExcluded();
|
||||
TestLevelCheckpoints();
|
||||
TestAabbOverlap();
|
||||
TestGravityAccumulation();
|
||||
TestGroundDetection();
|
||||
TestOneWayPlatform();
|
||||
TestLightPlatformActivation();
|
||||
TestShadowPlatformActivation();
|
||||
TestLightDoorOpen();
|
||||
TestLightEffectIgnoresOtherTypes();
|
||||
TestLevelLoaderBasic();
|
||||
std::cout << "game_engine_tests: PASS\n";
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user