Added weapon scripting system, added shotgun as example.

This commit is contained in:
Ethan Adams 2024-06-23 14:21:52 -04:00
parent fca7d0f99f
commit cefcb3889b
14 changed files with 200 additions and 44 deletions

View file

@ -34,7 +34,7 @@
</spriteids> </spriteids>
</map> </map>
<entities> <entities>
<player x="7" y="5" weapon="machineGun"> <player x="7" y="5" weapon="shotGun">
<sprite file="sprites/player3Atlas.png" framesize="64.0" directional="true"/> <sprite file="sprites/player3Atlas.png" framesize="64.0" directional="true"/>
</player> </player>
<entity x="10" y="3" weapon="pistolGun"> <entity x="10" y="3" weapon="pistolGun">

View file

@ -0,0 +1,17 @@
-- How bullets are produced make sure to generate bullet data, then pass it through createBullet
function onShoot()
rot = weapon.wielder.rotation
data = weapon:genBulletData()
for i=1, 10 do
spread = math.random(-20, 20)
print("direction x: "..data.direction.x.." y: "..data.direction.y);
data.direction.x = math.cos(math.rad(rot + spread))
data.direction.y = math.sin(math.rad(rot + spread))
weapon:createBullet(data)
end
end
-- How the bulllet, target or wielder respond when the bullet hits a target.
function onHit(target)
end

View file

@ -11,6 +11,21 @@
</bullet> </bullet>
</weapon> </weapon>
<weapon name="shotGun" fireSpeed="750.0">
<script file="scripts/shotgun_script.lua"/>
<sprite file="sprites/machineGunAtlas256.png" animated="true" frameSize="256.0">
<size x="55.0" y="55.0"/>
<offset x="-30.0" y="0.0"/>
</sprite>
<bullet sprite="sprites/bullet.png" animated="false" frameSize="64.0">
<spread>50</spread>
<speed>60.0</speed>
<drop>550.0</drop>
<size x="10.0" y="10.0"/>
<modifier min="0.3" max="1.0"/>
</bullet>
</weapon>
<weapon name="machineGun" fireSpeed="50.0"> <weapon name="machineGun" fireSpeed="50.0">
<sprite file="sprites/machineGunAtlas256.png" animated="true" frameSize="256.0"> <sprite file="sprites/machineGunAtlas256.png" animated="true" frameSize="256.0">
<size x="55.0" y="55.0"/> <size x="55.0" y="55.0"/>

View file

@ -38,11 +38,15 @@ protected:
void loadDebugStoryScene() { /*not implemented yet*/ } void loadDebugStoryScene() { /*not implemented yet*/ }
private: private:
void hookSceneEvents();
std::shared_ptr<GameActor> getGameActorByID(const unsigned int ID);
SceneType type; SceneType type;
std::shared_ptr<Map> map; std::shared_ptr<Map> map;
//std::shared_ptr<TileSet> tileSet; //std::shared_ptr<TileSet> tileSet;
std::shared_ptr<GameActor> player; std::shared_ptr<GameActor> player;
std::vector<std::shared_ptr<Entity>> entities; std::unordered_map<unsigned int, std::shared_ptr<GameActor>> entities;
std::shared_ptr<Camera> camera; std::shared_ptr<Camera> camera;
std::shared_ptr<PhysicsEngine> physicsEngine; std::shared_ptr<PhysicsEngine> physicsEngine;

View file

@ -20,9 +20,10 @@ class BulletManager;
class EventManager; class EventManager;
class ResourceManager; class ResourceManager;
class GameActor; class GameActor;
class WeaponScript;
struct WeaponData; struct WeaponData;
class Weapon : public Entity class Weapon : public Entity, public std::enable_shared_from_this<Weapon>
{ {
public: public:
Weapon(const WeaponData* data, const std::shared_ptr<Shader>& weaponShader, const std::shared_ptr<Shader>& bulletShader, ResourceManager* resourceManager); Weapon(const WeaponData* data, const std::shared_ptr<Shader>& weaponShader, const std::shared_ptr<Shader>& bulletShader, ResourceManager* resourceManager);
@ -30,13 +31,12 @@ public:
void setWielder(const std::shared_ptr<GameActor>& wielder) { this->wielder = wielder; } void setWielder(const std::shared_ptr<GameActor>& wielder) { this->wielder = wielder; }
void addComponent(const std::shared_ptr<Component>& component); void addComponent(const std::shared_ptr<Component>& component);
void attachScript(const std::shared_ptr<WeaponScript>& script);
void hookEventManager(const std::shared_ptr<EventManager>& eventManager); void hookEventManager(const std::shared_ptr<EventManager>& eventManager);
void shoot(); void shoot();
void update(float deltaTime); void update(float deltaTime);
void render(const std::shared_ptr<Camera>& camera); void render(const std::shared_ptr<Camera>& camera);
private:
struct BulletData { struct BulletData {
glm::vec3 origin; glm::vec3 origin;
glm::vec2 direction; glm::vec2 direction;
@ -45,12 +45,22 @@ private:
float speedMod; float speedMod;
float dropMod; float dropMod;
}; };
void onHitCallback(std::shared_ptr<GameActor> target);
glm::vec2 getWeaponSize() const { return weaponSize; }
// This is the offset from the center right point of the weapon to the barrel of the gun defined in
// the XML file for the weapon
glm::vec2 getWeaponOffset() const { return weaponOffset; }
float getBulletSpeed() const { return bulletSpeed; }
glm::vec2 getBulletSize() const { return bulletSize; }
std::shared_ptr<GameActor> getWielder() const { return wielder; }
Weapon::BulletData genBulletData ();
void createBullet (const BulletData& data);
private:
void adjustWeapon(); void adjustWeapon();
BulletData genBulletData();
std::shared_ptr<Bullet> createBullet(const BulletData& data);
glm::vec2 weaponSize; glm::vec2 weaponSize;
glm::vec2 weaponOffset; glm::vec2 weaponOffset;
@ -73,6 +83,7 @@ private:
Direction lastDir; Direction lastDir;
std::vector <std::shared_ptr<Component>> components; std::vector <std::shared_ptr<Component>> components;
std::shared_ptr<WeaponScript> weaponScript;
}; };

View file

@ -13,6 +13,7 @@ class Shader;
class Weapon; class Weapon;
class Script; class Script;
class AIScript; class AIScript;
class WeaponScript;
class TileSet; class TileSet;
class SpriteComponent; class SpriteComponent;
@ -30,6 +31,7 @@ public:
std::shared_ptr<Sprite> loadSpriteDirAnimated (const std::string& path, float frameSize); std::shared_ptr<Sprite> loadSpriteDirAnimated (const std::string& path, float frameSize);
std::shared_ptr<Sprite> loadSpriteStatic (const std::string& path); std::shared_ptr<Sprite> loadSpriteStatic (const std::string& path);
std::shared_ptr<AIScript> loadAIScript (const std::string& path); std::shared_ptr<AIScript> loadAIScript (const std::string& path);
std::shared_ptr<WeaponScript> loadWeaponScript (const std::string& path);
std::shared_ptr<TileSet> loadTileSet (const std::string& path, float frameSize); std::shared_ptr<TileSet> loadTileSet (const std::string& path, float frameSize);
std::shared_ptr<Shader> loadShader (const std::string& name, const std::string& vertexPath, const std::string& fragPath); std::shared_ptr<Shader> loadShader (const std::string& name, const std::string& vertexPath, const std::string& fragPath);

View file

@ -21,7 +21,7 @@ private:
} }
return result.valid(); return result.valid();
} }
virtual void registerUserTypes() {}; void registerGlobalUserTypes();
}; };
class AIScript : public Script { class AIScript : public Script {
@ -30,7 +30,14 @@ public:
private: private:
virtual void registerUserTypes() override; void registerUserTypes() ;
};
class WeaponScript : public Script {
public:
WeaponScript(const std::string& path) : Script(path) { registerUserTypes(); }
private:
void registerUserTypes() ;
}; };
#endif // _H_SCRIPT_H #endif // _H_SCRIPT_H

View file

@ -37,6 +37,7 @@ struct SceneData {
struct WeaponData { struct WeaponData {
std::string name; std::string name;
float fireSpeed = 250.0f; float fireSpeed = 250.0f;
std::string script = "";
std::string sprite; std::string sprite;
bool animated = false; bool animated = false;
float frameSize; float frameSize;

View file

@ -103,7 +103,7 @@ void Scene::loadDebugShooterScene()
entity->addComponent(std::make_shared<AIComponent>(ai)); entity->addComponent(std::make_shared<AIComponent>(ai));
} }
} }
entities.push_back(entity); entities.emplace(entity->getActorID(), entity);
} }
physicsEngine->loadCollisionMap(map->getCollisionMap(), sceneData->map.tileSize); physicsEngine->loadCollisionMap(map->getCollisionMap(), sceneData->map.tileSize);
@ -118,7 +118,7 @@ std::shared_ptr<GameActor> Scene::getPlayer() const
void Scene::update(float deltaTime) void Scene::update(float deltaTime)
{ {
for (auto& e : entities) for (auto& [id, e] : entities)
{ {
e->update(deltaTime); e->update(deltaTime);
if (camera->getTarget() == e.get()) if (camera->getTarget() == e.get())
@ -131,7 +131,7 @@ void Scene::update(float deltaTime)
void Scene::render() void Scene::render()
{ {
map->render(camera); map->render(camera);
for (auto& e : entities) for (auto& [id, e] : entities)
{ {
e->render(camera); e->render(camera);
} }
@ -143,3 +143,21 @@ void Scene::unloadScene()
//xmlLoader.reset(); //xmlLoader.reset();
} }
void Scene::hookSceneEvents()
{
eventManager->subscribe("OnBulletCollide", [this](std::shared_ptr<Event> e) {
auto collideEvent = std::static_pointer_cast<BulletCollideEvent>(e);
std::shared_ptr<GameActor> shooter = getGameActorByID(collideEvent->ownerID);
std::shared_ptr<GameActor> target = getGameActorByID(collideEvent->victimID);
if (shooter && target)
shooter->getHeldWeapon()->onHitCallback(target);
});
}
std::shared_ptr<GameActor> Scene::getGameActorByID(const unsigned int ID)
{
auto iterator = entities.find(ID);
if (iterator == entities.end())
return nullptr;
return iterator->second;
}

View file

@ -44,8 +44,8 @@ void BulletManager::update(float deltaTime)
{ {
if (!bullet) continue; if (!bullet) continue;
bullet->update(deltaTime); bullet->update(deltaTime);
float distance = glm::distance(bullet->getPosition(), bullet->getBulletOrigin()); float distance = glm::distance(bullet->getPhysicsComponent()->rigidBody.position, bullet->getBulletOrigin());
if (distance > bullet->getBulletDrop()) if (distance > bullet->getBulletDrop() || glm::length(bullet->getPhysicsComponent()->rigidBody.velocity) < 100.0f)
{ {
if (eventManager) if (eventManager)
eventManager->notify(std::make_shared<BulletDiedEvent>(bullet->getPhysicsComponent())); eventManager->notify(std::make_shared<BulletDiedEvent>(bullet->getPhysicsComponent()));

View file

@ -1,17 +1,19 @@
#include "gameplay/weapons/weapon.h" #include "gameplay/weapons/weapon.h"
#include "utility/resourcemanager.h"
#include "gameplay/weapons/bulletmanager.h" #include "gameplay/weapons/bulletmanager.h"
#include "gameplay/weapons/bullet.h" #include "gameplay/weapons/bullet.h"
#include "gameplay/gameactor.h" #include "gameplay/gameactor.h"
#include "utility/component.h"
#include "utility/events.h"
#include "gameplay/physics.h" #include "gameplay/physics.h"
#include <SDL_timer.h> #include <SDL_timer.h>
#include "utility/debugdraw.h" #include "utility/debugdraw.h"
#include "utility/component.h"
#include "utility/events.h"
#include "utility/resourcemanager.h"
#include "utility/script.h"
// TODO: Regular clean up, make this mess readable! // TODO: Regular clean up, make this mess readable!
// TODO: Attach script to weapons, create custom shoot scripts, allowing the addition of shotguns and other whacky things // TODO: Attach script to weapons, create custom shoot scripts, allowing the addition of shotguns and other whacky things
// TODO: Finally just allow the XMLLoader to read the weaponscript supplied and load it into the weapon
Weapon::Weapon(const WeaponData* data, const std::shared_ptr<Shader>& weaponShader, const std::shared_ptr<Shader>& bulletShader, ResourceManager* resourceManager) Weapon::Weapon(const WeaponData* data, const std::shared_ptr<Shader>& weaponShader, const std::shared_ptr<Shader>& bulletShader, ResourceManager* resourceManager)
: :
@ -51,10 +53,21 @@ void Weapon::shoot()
{ {
Uint32 currentTime = SDL_GetTicks(); Uint32 currentTime = SDL_GetTicks();
if (currentTime - lastFireTime >= fireSpeed) if (currentTime - lastFireTime >= fireSpeed)
{
if (!weaponScript || !weaponScript->lua["onShoot"].valid())
{ {
// create bullet using this generated data // create bullet using this generated data
BulletData b = genBulletData(); BulletData b = genBulletData();
bulletManager->addBullet(createBullet(b)); createBullet(b);
}
else {
auto result = weaponScript->lua["onShoot"]();
if (!result.valid())
{
sol::error err = result;
std::cerr << "lua error: " << err.what() << std::endl;
}
}
lastFireTime = currentTime; lastFireTime = currentTime;
} }
} }
@ -66,6 +79,26 @@ void Weapon::hookEventManager(const std::shared_ptr<EventManager>& eventManager)
bulletManager->hookEventManager(eventManager); bulletManager->hookEventManager(eventManager);
} }
void Weapon::attachScript(const std::shared_ptr<WeaponScript>& script)
{
weaponScript = script;
weaponScript->lua["weapon"] = shared_from_this();
std::cout << "weapon state bound" << std::endl;
}
void Weapon::onHitCallback(std::shared_ptr<GameActor> target)
{
if (weaponScript && weaponScript->lua["onHit"].valid())
{
auto result = weaponScript->lua["onHit"](target);
if (!result.valid())
{
sol::error err = result;
std::cerr << "lua error: " << err.what() << std::endl;
}
}
}
void Weapon::update(float deltaTime) void Weapon::update(float deltaTime)
{ {
Entity::update(deltaTime); Entity::update(deltaTime);
@ -130,7 +163,7 @@ Weapon::BulletData Weapon::genBulletData()
b.speedMod = bulletModifer->genFloat(); b.speedMod = bulletModifer->genFloat();
b.dropMod = bulletModifer->genFloat(); b.dropMod = bulletModifer->genFloat();
float radius = wielder->getScale().x + (weaponSize.x * 0.5) + weaponOffset.x; double radius = wielder->getScale().x + (weaponSize.x * 0.5) + weaponOffset.x;
b.origin = glm::vec3( b.origin = glm::vec3(
// x offset from the wielder // x offset from the wielder
@ -144,7 +177,7 @@ Weapon::BulletData Weapon::genBulletData()
return b; return b;
} }
std::shared_ptr<Bullet> Weapon::createBullet(const Weapon::BulletData& data) void Weapon::createBullet(const Weapon::BulletData& data)
{ {
auto bullet = std::make_shared<Bullet>(wielder->getActorID(), bulletShader, data.origin, data.direction, bulletSpeed, bulletDrop, bulletSize); auto bullet = std::make_shared<Bullet>(wielder->getActorID(), bulletShader, data.origin, data.direction, bulletSpeed, bulletDrop, bulletSize);
bullet->addComponent(bulletSprite); bullet->addComponent(bulletSprite);
@ -153,6 +186,6 @@ std::shared_ptr<Bullet> Weapon::createBullet(const Weapon::BulletData& data)
if (eventManager) if (eventManager)
eventManager->notify(std::make_shared<BulletFiredEvent>(bullet)); eventManager->notify(std::make_shared<BulletFiredEvent>(bullet));
return bullet; bulletManager->addBullet(bullet);
} }

View file

@ -37,6 +37,11 @@ std::shared_ptr<AIScript> ResourceManager::loadAIScript(const std::string& path)
return std::make_shared<AIScript>(path.c_str()); return std::make_shared<AIScript>(path.c_str());
} }
std::shared_ptr<WeaponScript> ResourceManager::loadWeaponScript(const std::string& path)
{
return std::make_shared<WeaponScript>(path.c_str());
}
std::shared_ptr<TileSet> ResourceManager::loadTileSet(const std::string& path, float frameSize) std::shared_ptr<TileSet> ResourceManager::loadTileSet(const std::string& path, float frameSize)
{ {
auto iterator = tileSets.find(path); auto iterator = tileSets.find(path);
@ -57,9 +62,15 @@ std::shared_ptr<Shader> ResourceManager::loadShader(const std::string& name, con
return shader; return shader;
} }
// We attach our script after we create our weapon because we are passing a reference to the weapon into the script and we don't want to pass an
// incomplete reference to our script.
std::shared_ptr<Weapon> ResourceManager::loadWeapon(const std::string& name, std::shared_ptr<Shader> weaponShader, std::shared_ptr<Shader> shader) std::shared_ptr<Weapon> ResourceManager::loadWeapon(const std::string& name, std::shared_ptr<Shader> weaponShader, std::shared_ptr<Shader> shader)
{ {
return std::make_shared<Weapon>(xmlLoader->getWeaponDataByName(name.c_str()), weaponShader, shader, this); const WeaponData* data = xmlLoader->getWeaponDataByName(name.c_str());
auto weapon = std::make_shared<Weapon>(data, weaponShader, shader, this);
if (!data->script.empty())
weapon->attachScript(loadWeaponScript(data->script));
return weapon;
} }
std::shared_ptr<SceneData> ResourceManager::loadScene(const std::string& id) std::shared_ptr<SceneData> ResourceManager::loadScene(const std::string& id)

View file

@ -2,27 +2,21 @@
#include "gameplay/gameactor.h" #include "gameplay/gameactor.h"
#include "gameplay/physics.h" #include "gameplay/physics.h"
#include "gameplay/ai.h" #include "gameplay/ai.h"
#include "gameplay/weapons/weapon.h"
#include "utility/raycaster.h" #include "utility/raycaster.h"
void AIScript::registerUserTypes() void Script::registerGlobalUserTypes()
{ {
// These usertypes are constant for every script
lua.new_usertype<glm::vec3>("vec3", lua.new_usertype<glm::vec3>("vec3",
sol::constructors<glm::vec3(), glm::vec3(float, float, float)>(), sol::constructors<glm::vec3(), glm::vec3(float, float, float)>(),
"x", &glm::vec3::x, "x", &glm::vec3::x,
"y", &glm::vec3::y, "y", &glm::vec3::y,
"z", &glm::vec3::z); "z", &glm::vec3::z);
lua["AIState"] = lua.create_table_with( lua.new_usertype<glm::vec2>("vec2",
"Idle", AIState::Idle, sol::constructors<glm::vec2(), glm::vec2(float, float)>(),
"Patrol", AIState::Patrol, "x", &glm::vec2::x,
"Alert", AIState::Alert "y", &glm::vec2::y);
);
lua.new_usertype<AI>("AI",
"state", sol::property(&AI::getState, &AI::setState));
lua.new_usertype<Raycaster>("Raycaster",
"performRaycast", &Raycaster::performRaycast,
"bresenhamRaycast", &Raycaster::bresenhamRaycast,
"distFromWall", &Raycaster::getDistanceFromWall,
"tileSize", &Raycaster::getTileSize);
lua.new_usertype<GameActor>("GameActor", lua.new_usertype<GameActor>("GameActor",
// properties // properties
"position", sol::property(&GameActor::getCenter, &GameActor::setPosition), "position", sol::property(&GameActor::getCenter, &GameActor::setPosition),
@ -43,8 +37,44 @@ void AIScript::registerUserTypes()
"applyForce", &PhysicsComponent::RigidBody::applyForce); "applyForce", &PhysicsComponent::RigidBody::applyForce);
} }
void AIScript::registerUserTypes()
{
lua["AIState"] = lua.create_table_with(
"Idle", AIState::Idle,
"Patrol", AIState::Patrol,
"Alert", AIState::Alert
);
lua.new_usertype<AI>("AI",
"state", sol::property(&AI::getState, &AI::setState));
lua.new_usertype<Raycaster>("Raycaster",
"performRaycast", &Raycaster::performRaycast,
"bresenhamRaycast", &Raycaster::bresenhamRaycast,
"distFromWall", &Raycaster::getDistanceFromWall,
"tileSize", &Raycaster::getTileSize);
}
void WeaponScript::registerUserTypes()
{
lua.new_usertype<Weapon>("Weapon",
"weaponSize", sol::property(&Weapon::getWeaponSize),
"weaponOffset", sol::property(&Weapon::getWeaponOffset),
"bulletSize", sol::property(&Weapon::getBulletSize),
"bulletSpeed", sol::property(&Weapon::getBulletSpeed),
"wielder", sol::property(&Weapon::getWielder),
"genBulletData", &Weapon::genBulletData,
"createBullet", &Weapon::createBullet);
lua.new_usertype<Weapon::BulletData>("BulletData",
"origin", &Weapon::BulletData::origin,
"direction", &Weapon::BulletData::direction,
"mass", &Weapon::BulletData::mass,
"sizeMod", &Weapon::BulletData::sizeMod,
"speedMod", &Weapon::BulletData::speedMod,
"dropMod", &Weapon::BulletData::dropMod);
}
Script::Script(const std::string& path) Script::Script(const std::string& path)
{ {
lua.open_libraries(sol::lib::base, sol::lib::math); lua.open_libraries(sol::lib::base, sol::lib::math);
registerGlobalUserTypes();
loadScript(path); loadScript(path);
} }

View file

@ -207,11 +207,17 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
{ {
// populate weapon data into this temp buffer, then push it into our list of weapons // populate weapon data into this temp buffer, then push it into our list of weapons
WeaponData data; WeaponData data;
const char* name, * sprite, * bulletSprite; const char* name, * sprite, * bulletSprite, * script;
// Getting top level weapon data, attribs in the weapon Node // Getting top level weapon data, attribs in the weapon Node
if (weapon->QueryStringAttribute("name", &name) != tinyxml2::XML_SUCCESS) if (weapon->QueryStringAttribute("name", &name) != tinyxml2::XML_SUCCESS)
continue; continue;
weapon->QueryFloatAttribute("fireSpeed", &data.fireSpeed); weapon->QueryFloatAttribute("fireSpeed", &data.fireSpeed);
// Getting script file if Node exists
tinyxml2::XMLElement* wepScript = weapon->FirstChildElement("script");
if (wepScript)
wepScript->QueryStringAttribute("file", &script);
else
script = "";
// Getting weapon sprite information, held in the sprite node // Getting weapon sprite information, held in the sprite node
tinyxml2::XMLElement* wepSprite = weapon->FirstChildElement("sprite"); tinyxml2::XMLElement* wepSprite = weapon->FirstChildElement("sprite");
if (wepSprite) if (wepSprite)
@ -248,6 +254,7 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
continue; continue;
data.name = name; data.name = name;
data.script = script;
data.sprite = sprite; data.sprite = sprite;
data.bulletSprite = bulletSprite; data.bulletSprite = bulletSprite;