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>
</map>
<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"/>
</player>
<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>
</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">
<sprite file="sprites/machineGunAtlas256.png" animated="true" frameSize="256.0">
<size x="55.0" y="55.0"/>

View file

@ -38,11 +38,15 @@ protected:
void loadDebugStoryScene() { /*not implemented yet*/ }
private:
void hookSceneEvents();
std::shared_ptr<GameActor> getGameActorByID(const unsigned int ID);
SceneType type;
std::shared_ptr<Map> map;
//std::shared_ptr<TileSet> tileSet;
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<PhysicsEngine> physicsEngine;

View file

@ -20,9 +20,10 @@ class BulletManager;
class EventManager;
class ResourceManager;
class GameActor;
class WeaponScript;
struct WeaponData;
class Weapon : public Entity
class Weapon : public Entity, public std::enable_shared_from_this<Weapon>
{
public:
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 addComponent(const std::shared_ptr<Component>& component);
void attachScript(const std::shared_ptr<WeaponScript>& script);
void hookEventManager(const std::shared_ptr<EventManager>& eventManager);
void shoot();
void update(float deltaTime);
void render(const std::shared_ptr<Camera>& camera);
private:
struct BulletData {
glm::vec3 origin;
glm::vec2 direction;
@ -45,12 +45,22 @@ private:
float speedMod;
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();
BulletData genBulletData();
std::shared_ptr<Bullet> createBullet(const BulletData& data);
glm::vec2 weaponSize;
glm::vec2 weaponOffset;
@ -73,6 +83,7 @@ private:
Direction lastDir;
std::vector <std::shared_ptr<Component>> components;
std::shared_ptr<WeaponScript> weaponScript;
};

View file

@ -13,6 +13,7 @@ class Shader;
class Weapon;
class Script;
class AIScript;
class WeaponScript;
class TileSet;
class SpriteComponent;
@ -26,11 +27,12 @@ public:
xmlLoader->loadScenes("scenes");
};
std::shared_ptr<Sprite> loadSpriteAnimated (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<AIScript> loadAIScript (const std::string& path);
std::shared_ptr<TileSet> loadTileSet (const std::string& path, float frameSize);
std::shared_ptr<Sprite> loadSpriteAnimated (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<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<Shader> loadShader (const std::string& name, const std::string& vertexPath, const std::string& fragPath);
std::shared_ptr<Weapon> loadWeapon (const std::string& name, std::shared_ptr<Shader> weaponShader, std::shared_ptr<Shader> bulletShader);

View file

@ -21,7 +21,7 @@ private:
}
return result.valid();
}
virtual void registerUserTypes() {};
void registerGlobalUserTypes();
};
class AIScript : public Script {
@ -30,7 +30,14 @@ public:
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

View file

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

View file

@ -103,7 +103,7 @@ void Scene::loadDebugShooterScene()
entity->addComponent(std::make_shared<AIComponent>(ai));
}
}
entities.push_back(entity);
entities.emplace(entity->getActorID(), entity);
}
physicsEngine->loadCollisionMap(map->getCollisionMap(), sceneData->map.tileSize);
@ -118,7 +118,7 @@ std::shared_ptr<GameActor> Scene::getPlayer() const
void Scene::update(float deltaTime)
{
for (auto& e : entities)
for (auto& [id, e] : entities)
{
e->update(deltaTime);
if (camera->getTarget() == e.get())
@ -131,7 +131,7 @@ void Scene::update(float deltaTime)
void Scene::render()
{
map->render(camera);
for (auto& e : entities)
for (auto& [id, e] : entities)
{
e->render(camera);
}
@ -143,3 +143,21 @@ void Scene::unloadScene()
//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;
bullet->update(deltaTime);
float distance = glm::distance(bullet->getPosition(), bullet->getBulletOrigin());
if (distance > bullet->getBulletDrop())
float distance = glm::distance(bullet->getPhysicsComponent()->rigidBody.position, bullet->getBulletOrigin());
if (distance > bullet->getBulletDrop() || glm::length(bullet->getPhysicsComponent()->rigidBody.velocity) < 100.0f)
{
if (eventManager)
eventManager->notify(std::make_shared<BulletDiedEvent>(bullet->getPhysicsComponent()));

View file

@ -1,17 +1,19 @@
#include "gameplay/weapons/weapon.h"
#include "utility/resourcemanager.h"
#include "gameplay/weapons/bulletmanager.h"
#include "gameplay/weapons/bullet.h"
#include "gameplay/gameactor.h"
#include "utility/component.h"
#include "utility/events.h"
#include "gameplay/physics.h"
#include <SDL_timer.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: 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)
:
@ -52,9 +54,20 @@ void Weapon::shoot()
Uint32 currentTime = SDL_GetTicks();
if (currentTime - lastFireTime >= fireSpeed)
{
// create bullet using this generated data
BulletData b = genBulletData();
bulletManager->addBullet(createBullet(b));
if (!weaponScript || !weaponScript->lua["onShoot"].valid())
{
// create bullet using this generated data
BulletData b = genBulletData();
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;
}
}
@ -66,6 +79,26 @@ void Weapon::hookEventManager(const std::shared_ptr<EventManager>& 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)
{
Entity::update(deltaTime);
@ -130,7 +163,7 @@ Weapon::BulletData Weapon::genBulletData()
b.speedMod = 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(
// x offset from the wielder
@ -144,7 +177,7 @@ Weapon::BulletData Weapon::genBulletData()
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);
bullet->addComponent(bulletSprite);
@ -153,6 +186,6 @@ std::shared_ptr<Bullet> Weapon::createBullet(const Weapon::BulletData& data)
if (eventManager)
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());
}
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)
{
auto iterator = tileSets.find(path);
@ -57,9 +62,15 @@ std::shared_ptr<Shader> ResourceManager::loadShader(const std::string& name, con
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)
{
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)

View file

@ -2,27 +2,21 @@
#include "gameplay/gameactor.h"
#include "gameplay/physics.h"
#include "gameplay/ai.h"
#include "gameplay/weapons/weapon.h"
#include "utility/raycaster.h"
void AIScript::registerUserTypes()
void Script::registerGlobalUserTypes()
{
// These usertypes are constant for every script
lua.new_usertype<glm::vec3>("vec3",
sol::constructors<glm::vec3(), glm::vec3(float, float, float)>(),
"x", &glm::vec3::x,
"y", &glm::vec3::y,
"z", &glm::vec3::z);
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);
lua.new_usertype<glm::vec2>("vec2",
sol::constructors<glm::vec2(), glm::vec2(float, float)>(),
"x", &glm::vec2::x,
"y", &glm::vec2::y);
lua.new_usertype<GameActor>("GameActor",
// properties
"position", sol::property(&GameActor::getCenter, &GameActor::setPosition),
@ -43,8 +37,44 @@ void AIScript::registerUserTypes()
"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)
{
lua.open_libraries(sol::lib::base, sol::lib::math);
registerGlobalUserTypes();
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
WeaponData data;
const char* name, * sprite, * bulletSprite;
const char* name, * sprite, * bulletSprite, * script;
// Getting top level weapon data, attribs in the weapon Node
if (weapon->QueryStringAttribute("name", &name) != tinyxml2::XML_SUCCESS)
continue;
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
tinyxml2::XMLElement* wepSprite = weapon->FirstChildElement("sprite");
if (wepSprite)
@ -248,6 +254,7 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
continue;
data.name = name;
data.script = script;
data.sprite = sprite;
data.bulletSprite = bulletSprite;