The Snivy Video Game Is Complete
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include "../resource/texture.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace glm;
|
||||
using namespace game::util;
|
||||
@@ -16,20 +17,70 @@ using namespace game::anm2;
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
std::shared_ptr<void> texture_callback(const std::filesystem::path& path) { return std::make_shared<Texture>(path); }
|
||||
std::shared_ptr<void> sound_callback(const std::filesystem::path& path) { return std::make_shared<Audio>(path); }
|
||||
|
||||
Actor::Actor(const std::filesystem::path& path, vec2 position) : anm2(path, texture_callback, sound_callback)
|
||||
Actor::Actor(Anm2* _anm2, vec2 _position, Mode mode, float time) : anm2(_anm2), position(_position)
|
||||
{
|
||||
this->position = position;
|
||||
play(anm2.animations.defaultAnimation);
|
||||
if (anm2)
|
||||
{
|
||||
this->mode = mode;
|
||||
this->startTime = time;
|
||||
play(anm2->animations.defaultAnimation, mode, time);
|
||||
}
|
||||
}
|
||||
|
||||
anm2::Animation* Actor::animation_get() { return vector::find(anm2.animations.items, animationIndex); }
|
||||
|
||||
anm2::Item* Actor::item_get(anm2::Type type, int id)
|
||||
anm2::Animation* Actor::animation_get(int index)
|
||||
{
|
||||
if (auto animation = animation_get())
|
||||
if (!anm2) return nullptr;
|
||||
if (index == -1) index = animationIndex;
|
||||
if (anm2->animations.mapReverse.contains(index)) return &anm2->animations.items[index];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
anm2::Animation* Actor::animation_get(const std::string& name)
|
||||
{
|
||||
if (!anm2) return nullptr;
|
||||
if (anm2->animations.map.contains(name)) return &anm2->animations.items[anm2->animations.map[name]];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Actor::is_playing(const std::string& name)
|
||||
{
|
||||
if (!anm2) return false;
|
||||
if (name.empty())
|
||||
return isPlaying;
|
||||
else
|
||||
return isPlaying && anm2->animations.map[name] == animationIndex;
|
||||
}
|
||||
|
||||
int Actor::animation_index_get(const std::string& name)
|
||||
{
|
||||
if (!anm2) return -1;
|
||||
if (anm2->animations.map.contains(name)) return anm2->animations.map[name];
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Actor::item_id_get(const std::string& name, anm2::Type type)
|
||||
{
|
||||
if (!anm2 || (type != anm2::LAYER && type != anm2::NULL_)) return -1;
|
||||
|
||||
if (type == anm2::LAYER)
|
||||
{
|
||||
for (int i = 0; i < anm2->content.layers.size(); i++)
|
||||
if (anm2->content.layers.at(i).name == name) return i;
|
||||
}
|
||||
else if (type == anm2::NULL_)
|
||||
{
|
||||
for (int i = 0; i < anm2->content.nulls.size(); i++)
|
||||
if (anm2->content.nulls.at(i).name == name) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
anm2::Item* Actor::item_get(anm2::Type type, int id, int animationIndex)
|
||||
{
|
||||
if (!anm2) return nullptr;
|
||||
if (animationIndex == -1) animationIndex = this->animationIndex;
|
||||
if (auto animation = animation_get(animationIndex))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
@@ -51,6 +102,16 @@ namespace game::resource
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Actor::item_length(anm2::Item* item)
|
||||
{
|
||||
if (!item) return -1;
|
||||
|
||||
int duration{};
|
||||
for (auto& frame : item->frames)
|
||||
duration += frame.duration;
|
||||
return duration;
|
||||
}
|
||||
|
||||
anm2::Frame* Actor::trigger_get(int atFrame)
|
||||
{
|
||||
if (auto item = item_get(anm2::TRIGGER))
|
||||
@@ -66,7 +127,14 @@ namespace game::resource
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
anm2::Frame Actor::frame_generate(anm2::Item& item, float time)
|
||||
bool Actor::is_event(const std::string& event)
|
||||
{
|
||||
if (!anm2) return false;
|
||||
if (playedEventID == -1) return false;
|
||||
return event == anm2->content.events.at(playedEventID).name;
|
||||
}
|
||||
|
||||
anm2::Frame Actor::frame_generate(anm2::Item& item, float time, anm2::Type type, int id)
|
||||
{
|
||||
anm2::Frame frame{};
|
||||
frame.isVisible = false;
|
||||
@@ -76,6 +144,7 @@ namespace game::resource
|
||||
time = time < 0.0f ? 0.0f : time;
|
||||
|
||||
anm2::Frame* frameNext = nullptr;
|
||||
anm2::Frame frameNextCopy{};
|
||||
int durationCurrent = 0;
|
||||
int durationNext = 0;
|
||||
|
||||
@@ -90,7 +159,10 @@ namespace game::resource
|
||||
if (time >= durationCurrent && time < durationNext)
|
||||
{
|
||||
if (i + 1 < (int)item.frames.size())
|
||||
{
|
||||
frameNext = &item.frames[i + 1];
|
||||
frameNextCopy = *frameNext;
|
||||
}
|
||||
else
|
||||
frameNext = nullptr;
|
||||
break;
|
||||
@@ -99,53 +171,110 @@ namespace game::resource
|
||||
durationCurrent += frame.duration;
|
||||
}
|
||||
|
||||
for (auto& override : overrides)
|
||||
{
|
||||
if (!override || !override->isEnabled) continue;
|
||||
|
||||
if (id == override->destinationID)
|
||||
{
|
||||
switch (override->mode)
|
||||
{
|
||||
case Override::FRAME_ADD:
|
||||
if (override->frame.scale.has_value())
|
||||
{
|
||||
frame.scale += *override->frame.scale;
|
||||
if (frameNext) frameNextCopy.scale += *override->frame.scale;
|
||||
}
|
||||
if (override->frame.rotation.has_value())
|
||||
{
|
||||
frame.rotation += *override->frame.rotation;
|
||||
if (frameNext) frameNextCopy.rotation += *override->frame.rotation;
|
||||
}
|
||||
break;
|
||||
case Override::FRAME_SET:
|
||||
if (override->frame.scale.has_value())
|
||||
{
|
||||
frame.scale = *override->frame.scale;
|
||||
if (frameNext) frameNextCopy.scale = *override->frame.scale;
|
||||
}
|
||||
if (override->frame.rotation.has_value())
|
||||
{
|
||||
frame.rotation = *override->frame.rotation;
|
||||
if (frameNext) frameNextCopy.rotation = *override->frame.rotation;
|
||||
}
|
||||
break;
|
||||
case Override::ITEM_SET:
|
||||
default:
|
||||
if (override->animationIndex == -1) break;
|
||||
auto& animation = anm2->animations.items[override->animationIndex];
|
||||
auto overrideFrame = frame_generate(animation.layerAnimations[override->sourceID], override->time,
|
||||
anm2::LAYER, override->sourceID);
|
||||
frame.crop = overrideFrame.crop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frame.isInterpolated && frameNext && frame.duration > 1)
|
||||
{
|
||||
auto interpolation = (time - durationCurrent) / (durationNext - durationCurrent);
|
||||
|
||||
frame.rotation = glm::mix(frame.rotation, frameNext->rotation, interpolation);
|
||||
frame.position = glm::mix(frame.position, frameNext->position, interpolation);
|
||||
frame.scale = glm::mix(frame.scale, frameNext->scale, interpolation);
|
||||
frame.colorOffset = glm::mix(frame.colorOffset, frameNext->colorOffset, interpolation);
|
||||
frame.tint = glm::mix(frame.tint, frameNext->tint, interpolation);
|
||||
frame.rotation = glm::mix(frame.rotation, frameNextCopy.rotation, interpolation);
|
||||
frame.position = glm::mix(frame.position, frameNextCopy.position, interpolation);
|
||||
frame.scale = glm::mix(frame.scale, frameNextCopy.scale, interpolation);
|
||||
frame.colorOffset = glm::mix(frame.colorOffset, frameNextCopy.colorOffset, interpolation);
|
||||
frame.tint = glm::mix(frame.tint, frameNextCopy.tint, interpolation);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void Actor::play(const std::string& name)
|
||||
void Actor::play(int index, Mode mode, float time, float speedMultiplier)
|
||||
{
|
||||
for (int i = 0; i < anm2.animations.items.size(); i++)
|
||||
{
|
||||
if (anm2.animations.items[i].name == name)
|
||||
{
|
||||
animationIndex = i;
|
||||
time = 0.0f;
|
||||
isPlaying = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->playedEventID = -1;
|
||||
this->playedTriggers.clear();
|
||||
|
||||
if (!anm2) return;
|
||||
if (mode != FORCE_PLAY && this->animationIndex == index) return;
|
||||
if (!vector::in_bounds(anm2->animations.items, index)) return;
|
||||
this->speedMultiplier = speedMultiplier;
|
||||
this->previousAnimationIndex = animationIndex;
|
||||
this->animationIndex = index;
|
||||
this->time = time;
|
||||
if (mode == PLAY || mode == FORCE_PLAY) isPlaying = true;
|
||||
}
|
||||
|
||||
void Actor::play(const std::string& name, Mode mode, float time, float speedMultiplier)
|
||||
{
|
||||
if (!anm2) return;
|
||||
if (anm2->animations.map.contains(name))
|
||||
play(anm2->animations.map.at(name), mode, time, speedMultiplier);
|
||||
else
|
||||
std::cout << "Animation \"" << name << "\" does not exist! Unable to play!\n";
|
||||
}
|
||||
|
||||
void Actor::tick()
|
||||
{
|
||||
if (!anm2) return;
|
||||
if (!isPlaying) return;
|
||||
auto animation = animation_get();
|
||||
if (!animation) return;
|
||||
|
||||
time += anm2.info.fps / 30.0f;
|
||||
playedEventID = -1;
|
||||
|
||||
auto intTime = (int)time;
|
||||
|
||||
if (auto trigger = trigger_get(intTime))
|
||||
for (auto& trigger : animation->triggers.frames)
|
||||
{
|
||||
if (!playedTriggers.contains(intTime))
|
||||
if (!playedTriggers.contains(trigger.atFrame) && time >= trigger.atFrame)
|
||||
{
|
||||
if (auto sound = map::find(anm2.content.sounds, trigger->soundID)) sound->audio.play();
|
||||
playedTriggers.insert(intTime);
|
||||
if (auto sound = map::find(anm2->content.sounds, trigger.soundID)) sound->audio.play();
|
||||
playedTriggers.insert((int)trigger.atFrame);
|
||||
playedEventID = trigger.eventID;
|
||||
}
|
||||
}
|
||||
|
||||
auto increment = (anm2->info.fps / 30.0f) * speedMultiplier;
|
||||
time += increment;
|
||||
|
||||
if (time >= animation->frameNum)
|
||||
{
|
||||
if (animation->isLoop)
|
||||
@@ -155,43 +284,138 @@ namespace game::resource
|
||||
|
||||
playedTriggers.clear();
|
||||
}
|
||||
|
||||
for (auto& override : overrides)
|
||||
{
|
||||
if (!override->isEnabled || override->length < 0) continue;
|
||||
override->time += increment;
|
||||
if (override->time > override->length) override->isLoop ? override->time = 0.0f : override->isEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Actor::render(Shader& shader, Canvas& canvas)
|
||||
glm::vec4 Actor::null_frame_rect(int nullID)
|
||||
{
|
||||
auto invalidRect = glm::vec4(0.0f / 0.0f);
|
||||
if (!anm2 || nullID == -1) return invalidRect;
|
||||
auto item = item_get(anm2::NULL_, nullID);
|
||||
if (!item) return invalidRect;
|
||||
|
||||
auto animation = animation_get();
|
||||
if (!animation) return invalidRect;
|
||||
|
||||
auto root = frame_generate(animation->rootAnimation, time, anm2::ROOT);
|
||||
|
||||
for (auto& override : overrides)
|
||||
{
|
||||
if (!override || !override->isEnabled || override->type != anm2::ROOT) continue;
|
||||
|
||||
switch (override->mode)
|
||||
{
|
||||
case Override::FRAME_ADD:
|
||||
if (override->frame.scale.has_value()) root.scale += *override->frame.scale;
|
||||
if (override->frame.rotation.has_value()) root.rotation += *override->frame.rotation;
|
||||
break;
|
||||
case Override::FRAME_SET:
|
||||
if (override->frame.scale.has_value()) root.scale = *override->frame.scale;
|
||||
if (override->frame.rotation.has_value()) root.rotation = *override->frame.rotation;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto frame = frame_generate(*item, time, anm2::NULL_, nullID);
|
||||
if (!frame.isVisible) return invalidRect;
|
||||
|
||||
auto rootScale = math::to_unit(root.scale);
|
||||
auto frameScale = math::to_unit(frame.scale);
|
||||
auto combinedScale = rootScale * frameScale;
|
||||
auto scaledSize = NULL_SIZE * glm::abs(combinedScale);
|
||||
|
||||
auto worldPosition = position + root.position + frame.position * rootScale;
|
||||
auto halfSize = scaledSize * 0.5f;
|
||||
|
||||
return glm::vec4(worldPosition - halfSize, scaledSize);
|
||||
}
|
||||
|
||||
void Actor::render(Shader& textureShader, Shader& rectShader, Canvas& canvas)
|
||||
{
|
||||
if (!anm2) return;
|
||||
auto animation = animation_get();
|
||||
if (!animation) return;
|
||||
|
||||
auto root = frame_generate(animation->rootAnimation, time);
|
||||
auto rootModel = math::quad_model_parent_get(root.position + position, root.pivot,
|
||||
math::percent_to_unit(root.scale), root.rotation);
|
||||
auto root = frame_generate(animation->rootAnimation, time, anm2::ROOT);
|
||||
|
||||
for (auto& override : overrides)
|
||||
{
|
||||
if (!override || !override->isEnabled || override->type != anm2::ROOT) continue;
|
||||
|
||||
switch (override->mode)
|
||||
{
|
||||
case Override::FRAME_ADD:
|
||||
if (override->frame.scale.has_value()) root.scale += *override->frame.scale;
|
||||
if (override->frame.rotation.has_value()) root.rotation += *override->frame.rotation;
|
||||
break;
|
||||
case Override::FRAME_SET:
|
||||
if (override->frame.scale.has_value()) root.scale = *override->frame.scale;
|
||||
if (override->frame.rotation.has_value()) root.rotation = *override->frame.rotation;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto rootModel =
|
||||
math::quad_model_parent_get(root.position + position, root.pivot, math::to_unit(root.scale), root.rotation);
|
||||
|
||||
for (auto& i : animation->layerOrder)
|
||||
{
|
||||
auto& layerAnimation = animation->layerAnimations[i];
|
||||
if (!layerAnimation.isVisible) continue;
|
||||
|
||||
auto layer = map::find(anm2.content.layers, i);
|
||||
auto layer = map::find(anm2->content.layers, i);
|
||||
if (!layer) continue;
|
||||
|
||||
auto spritesheet = map::find(anm2.content.spritesheets, layer->spritesheetID);
|
||||
auto spritesheet = map::find(anm2->content.spritesheets, layer->spritesheetID);
|
||||
if (!spritesheet) continue;
|
||||
|
||||
auto frame = frame_generate(layerAnimation, time);
|
||||
auto frame = frame_generate(layerAnimation, time, anm2::LAYER, i);
|
||||
if (!frame.isVisible) continue;
|
||||
|
||||
auto model = math::quad_model_get(frame.size, frame.position, frame.pivot, math::percent_to_unit(frame.scale),
|
||||
frame.rotation);
|
||||
auto model =
|
||||
math::quad_model_get(frame.size, frame.position, frame.pivot, math::to_unit(frame.scale), frame.rotation);
|
||||
model = rootModel * model;
|
||||
|
||||
auto& texture = spritesheet->texture;
|
||||
if (!texture.is_valid()) return;
|
||||
|
||||
auto tint = frame.tint * root.tint;
|
||||
auto colorOffset = frame.colorOffset + root.colorOffset;
|
||||
|
||||
auto uvMin = frame.crop / vec2(texture.size);
|
||||
auto uvMax = (frame.crop + frame.size) / vec2(texture.size);
|
||||
auto uvVertices = math::uv_vertices_get(uvMin, uvMax);
|
||||
|
||||
canvas.texture_render(shader, texture.id, model, frame.tint, frame.colorOffset, uvVertices.data());
|
||||
canvas.texture_render(textureShader, texture.id, model, tint, colorOffset, uvVertices.data());
|
||||
}
|
||||
|
||||
if (isShowNulls)
|
||||
{
|
||||
for (int i = 0; i < animation->nullAnimations.size(); i++)
|
||||
{
|
||||
auto& nullAnimation = animation->nullAnimations[i];
|
||||
if (!nullAnimation.isVisible) continue;
|
||||
|
||||
auto frame = frame_generate(nullAnimation, time, anm2::NULL_, i);
|
||||
if (!frame.isVisible) continue;
|
||||
|
||||
auto model = math::quad_model_get(frame.scale, frame.position, frame.scale * 0.5f, vec2(1.0f), frame.rotation);
|
||||
model = rootModel * model;
|
||||
|
||||
canvas.rect_render(rectShader, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Actor::consume_played_event() { playedEventID = -1; }
|
||||
};
|
||||
|
||||
@@ -11,23 +11,70 @@ namespace game::resource
|
||||
{
|
||||
|
||||
public:
|
||||
anm2::Anm2 anm2{};
|
||||
static constexpr auto NULL_SIZE = glm::vec2(100, 100);
|
||||
|
||||
enum Mode
|
||||
{
|
||||
PLAY,
|
||||
SET,
|
||||
FORCE_PLAY
|
||||
};
|
||||
|
||||
struct Override
|
||||
{
|
||||
enum Mode
|
||||
{
|
||||
ITEM_SET,
|
||||
FRAME_ADD,
|
||||
FRAME_SET
|
||||
};
|
||||
|
||||
anm2::FrameOptional frame{};
|
||||
int animationIndex{-1};
|
||||
int sourceID{-1};
|
||||
int destinationID{-1};
|
||||
float length{-1.0f};
|
||||
bool isLoop{false};
|
||||
Mode mode{ITEM_SET};
|
||||
anm2::Type type{anm2::LAYER};
|
||||
|
||||
bool isEnabled{true};
|
||||
float time{};
|
||||
};
|
||||
|
||||
anm2::Anm2* anm2{};
|
||||
glm::vec2 position{};
|
||||
float time{};
|
||||
bool isPlaying{};
|
||||
bool isShowNulls{};
|
||||
int animationIndex{-1};
|
||||
std::unordered_set<int> playedTriggers{};
|
||||
int previousAnimationIndex{-1};
|
||||
int lastPlayedAnimationIndex{-1};
|
||||
int playedEventID{-1};
|
||||
Mode mode{PLAY};
|
||||
float startTime{};
|
||||
float speedMultiplier{};
|
||||
|
||||
Actor(const std::filesystem::path&, glm::vec2);
|
||||
anm2::Animation* animation_get();
|
||||
anm2::Animation* animation_get(std::string&);
|
||||
int animation_index_get(anm2::Animation&);
|
||||
anm2::Item* item_get(anm2::Type, int = -1);
|
||||
std::unordered_set<int> playedTriggers{};
|
||||
std::vector<Override*> overrides{};
|
||||
|
||||
Actor(anm2::Anm2*, glm::vec2, Mode = PLAY, float = 0.0f);
|
||||
anm2::Animation* animation_get(int = -1);
|
||||
anm2::Animation* animation_get(const std::string&);
|
||||
int animation_index_get(const std::string&);
|
||||
anm2::Item* item_get(anm2::Type, int = -1, int = -1);
|
||||
int item_length(anm2::Item*);
|
||||
anm2::Frame* trigger_get(int);
|
||||
anm2::Frame* frame_get(int, anm2::Type, int = -1);
|
||||
anm2::Frame frame_generate(anm2::Item&, float);
|
||||
void play(const std::string&);
|
||||
int item_id_get(const std::string&, anm2::Type = anm2::LAYER);
|
||||
anm2::Frame frame_generate(anm2::Item&, float, anm2::Type, int = -1);
|
||||
void play(const std::string&, Mode = PLAY, float = 0.0f, float = 1.0f);
|
||||
void play(int, Mode = PLAY, float = 0.0f, float = 1.0f);
|
||||
bool is_event(const std::string& event);
|
||||
void tick();
|
||||
void render(Shader&, Canvas&);
|
||||
bool is_playing(const std::string& name = {});
|
||||
void render(Shader& textureShader, Shader& rectShader, Canvas&);
|
||||
glm::vec4 null_frame_rect(int = -1);
|
||||
void consume_played_event();
|
||||
};
|
||||
}
|
||||
@@ -1,46 +1,26 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "../util/xml_.h"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::resource;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::anm2
|
||||
{
|
||||
XMLError query_string_attribute(XMLElement* element, const char* attribute, std::string* value)
|
||||
{
|
||||
const char* temp = nullptr;
|
||||
auto result = element->QueryStringAttribute(attribute, &temp);
|
||||
if (result == XML_SUCCESS && temp && value) *value = temp;
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_path_attribute(XMLElement* element, const char* attribute, std::filesystem::path* value)
|
||||
{
|
||||
std::string temp{};
|
||||
auto result = query_string_attribute(element, attribute, &temp);
|
||||
if (value) *value = std::filesystem::path(temp);
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_color_attribute(XMLElement* element, const char* attribute, float* value)
|
||||
{
|
||||
int temp{};
|
||||
auto result = element->QueryIntAttribute(attribute, &temp);
|
||||
if (result == XML_SUCCESS && value) *value = (temp / 255.0f);
|
||||
return result;
|
||||
}
|
||||
|
||||
Info::Info(XMLElement* element)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Fps", &fps);
|
||||
}
|
||||
|
||||
Spritesheet::Spritesheet(XMLElement* element, int& id, TextureCallback textureCallback)
|
||||
Spritesheet::Spritesheet(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
query_path_attribute(element, "Path", &path);
|
||||
xml::query_path_attribute(element, "Path", &path);
|
||||
texture = Texture(path);
|
||||
}
|
||||
|
||||
@@ -48,7 +28,7 @@ namespace game::anm2
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
query_string_attribute(element, "Name", &name);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
element->QueryIntAttribute("SpritesheetId", &spritesheetID);
|
||||
}
|
||||
|
||||
@@ -56,7 +36,7 @@ namespace game::anm2
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
query_string_attribute(element, "Name", &name);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
element->QueryBoolAttribute("ShowRect", &isShowRect);
|
||||
}
|
||||
|
||||
@@ -64,18 +44,18 @@ namespace game::anm2
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
query_string_attribute(element, "Name", &name);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
}
|
||||
|
||||
Sound::Sound(XMLElement* element, int& id, SoundCallback soundCallback)
|
||||
Sound::Sound(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
query_path_attribute(element, "Path", &path);
|
||||
xml::query_path_attribute(element, "Path", &path);
|
||||
audio = Audio(path);
|
||||
}
|
||||
|
||||
Content::Content(XMLElement* element, TextureCallback textureCallback, SoundCallback soundCallback)
|
||||
Content::Content(XMLElement* element)
|
||||
{
|
||||
if (auto spritesheetsElement = element->FirstChildElement("Spritesheets"))
|
||||
{
|
||||
@@ -83,7 +63,7 @@ namespace game::anm2
|
||||
child = child->NextSiblingElement("Spritesheet"))
|
||||
{
|
||||
int spritesheetId{};
|
||||
Spritesheet spritesheet(child, spritesheetId, textureCallback);
|
||||
Spritesheet spritesheet(child, spritesheetId);
|
||||
spritesheets.emplace(spritesheetId, std::move(spritesheet));
|
||||
}
|
||||
}
|
||||
@@ -123,7 +103,7 @@ namespace game::anm2
|
||||
for (auto child = soundsElement->FirstChildElement("Sound"); child; child = child->NextSiblingElement("Sound"))
|
||||
{
|
||||
int soundId{};
|
||||
Sound sound(child, soundId, soundCallback);
|
||||
Sound sound(child, soundId);
|
||||
sounds.emplace(soundId, std::move(sound));
|
||||
}
|
||||
}
|
||||
@@ -148,13 +128,13 @@ namespace game::anm2
|
||||
element->QueryFloatAttribute("YScale", &scale.y);
|
||||
element->QueryIntAttribute("Delay", &duration);
|
||||
element->QueryBoolAttribute("Visible", &isVisible);
|
||||
query_color_attribute(element, "RedTint", &tint.r);
|
||||
query_color_attribute(element, "GreenTint", &tint.g);
|
||||
query_color_attribute(element, "BlueTint", &tint.b);
|
||||
query_color_attribute(element, "AlphaTint", &tint.a);
|
||||
query_color_attribute(element, "RedOffset", &colorOffset.r);
|
||||
query_color_attribute(element, "GreenOffset", &colorOffset.g);
|
||||
query_color_attribute(element, "BlueOffset", &colorOffset.b);
|
||||
xml::query_color_attribute(element, "RedTint", &tint.r);
|
||||
xml::query_color_attribute(element, "GreenTint", &tint.g);
|
||||
xml::query_color_attribute(element, "BlueTint", &tint.b);
|
||||
xml::query_color_attribute(element, "AlphaTint", &tint.a);
|
||||
xml::query_color_attribute(element, "RedOffset", &colorOffset.r);
|
||||
xml::query_color_attribute(element, "GreenOffset", &colorOffset.g);
|
||||
xml::query_color_attribute(element, "BlueOffset", &colorOffset.b);
|
||||
element->QueryFloatAttribute("Rotation", &rotation);
|
||||
element->QueryBoolAttribute("Interpolated", &isInterpolated);
|
||||
}
|
||||
@@ -180,7 +160,7 @@ namespace game::anm2
|
||||
|
||||
Animation::Animation(XMLElement* element)
|
||||
{
|
||||
query_string_attribute(element, "Name", &name);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
element->QueryIntAttribute("FrameNum", &frameNum);
|
||||
element->QueryBoolAttribute("Loop", &isLoop);
|
||||
|
||||
@@ -215,13 +195,20 @@ namespace game::anm2
|
||||
|
||||
Animations::Animations(XMLElement* element)
|
||||
{
|
||||
query_string_attribute(element, "DefaultAnimation", &defaultAnimation);
|
||||
xml::query_string_attribute(element, "DefaultAnimation", &defaultAnimation);
|
||||
|
||||
for (auto child = element->FirstChildElement("Animation"); child; child = child->NextSiblingElement("Animation"))
|
||||
items.emplace_back(Animation(child));
|
||||
|
||||
for (int i = 0; i < items.size(); i++)
|
||||
{
|
||||
auto& item = items.at(i);
|
||||
map[item.name] = i;
|
||||
mapReverse[i] = item.name;
|
||||
}
|
||||
}
|
||||
|
||||
Anm2::Anm2(const std::filesystem::path& path, TextureCallback textureCallback, SoundCallback soundCallback)
|
||||
Anm2::Anm2(const std::filesystem::path& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
@@ -231,18 +218,17 @@ namespace game::anm2
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Initialzed anm2: " << path.string() << "\n";
|
||||
|
||||
auto previousPath = std::filesystem::current_path();
|
||||
std::filesystem::current_path(path.parent_path());
|
||||
|
||||
auto element = document.RootElement();
|
||||
|
||||
if (auto infoElement = element->FirstChildElement("Info")) info = Info(infoElement);
|
||||
if (auto contentElement = element->FirstChildElement("Content"))
|
||||
content = Content(contentElement, textureCallback, soundCallback);
|
||||
if (auto contentElement = element->FirstChildElement("Content")) content = Content(contentElement);
|
||||
if (auto animationsElement = element->FirstChildElement("Animations")) animations = Animations(animationsElement);
|
||||
|
||||
std::filesystem::current_path(previousPath);
|
||||
|
||||
std::cout << "Initialzed anm2: " << path.string() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include <filesystem>
|
||||
@@ -25,9 +25,6 @@ namespace game::anm2
|
||||
TRIGGER
|
||||
};
|
||||
|
||||
using TextureCallback = std::function<std::shared_ptr<void>(const std::filesystem::path&)>;
|
||||
using SoundCallback = std::function<std::shared_ptr<void>(const std::filesystem::path&)>;
|
||||
|
||||
class Info
|
||||
{
|
||||
public:
|
||||
@@ -43,7 +40,7 @@ namespace game::anm2
|
||||
std::filesystem::path path{};
|
||||
resource::Texture texture{};
|
||||
|
||||
Spritesheet(tinyxml2::XMLElement*, int&, TextureCallback = nullptr);
|
||||
Spritesheet(tinyxml2::XMLElement*, int&);
|
||||
};
|
||||
|
||||
class Layer
|
||||
@@ -75,7 +72,7 @@ namespace game::anm2
|
||||
std::filesystem::path path{};
|
||||
resource::Audio audio{};
|
||||
|
||||
Sound(tinyxml2::XMLElement*, int&, SoundCallback = nullptr);
|
||||
Sound(tinyxml2::XMLElement*, int&);
|
||||
};
|
||||
|
||||
class Content
|
||||
@@ -88,7 +85,7 @@ namespace game::anm2
|
||||
std::map<int, Sound> sounds{};
|
||||
|
||||
Content() = default;
|
||||
Content(tinyxml2::XMLElement*, TextureCallback = nullptr, SoundCallback = nullptr);
|
||||
Content(tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
struct Frame
|
||||
@@ -113,6 +110,20 @@ namespace game::anm2
|
||||
Frame(tinyxml2::XMLElement*, Type);
|
||||
};
|
||||
|
||||
struct FrameOptional
|
||||
{
|
||||
std::optional<glm::vec2> crop{};
|
||||
std::optional<glm::vec2> position{};
|
||||
std::optional<glm::vec2> pivot{};
|
||||
std::optional<glm::vec2> size{};
|
||||
std::optional<glm::vec2> scale{};
|
||||
std::optional<float> rotation{};
|
||||
std::optional<glm::vec4> tint{};
|
||||
std::optional<glm::vec3> colorOffset{};
|
||||
std::optional<bool> isInterpolated{};
|
||||
std::optional<bool> isVisible{};
|
||||
};
|
||||
|
||||
class Item
|
||||
{
|
||||
public:
|
||||
@@ -145,6 +156,8 @@ namespace game::anm2
|
||||
public:
|
||||
std::string defaultAnimation{};
|
||||
std::vector<Animation> items{};
|
||||
std::unordered_map<std::string, int> map{};
|
||||
std::unordered_map<int, std::string> mapReverse{};
|
||||
|
||||
Animations() = default;
|
||||
Animations(tinyxml2::XMLElement*);
|
||||
@@ -158,6 +171,6 @@ namespace game::anm2
|
||||
Animations animations{};
|
||||
|
||||
Anm2() = default;
|
||||
Anm2(const std::filesystem::path&, TextureCallback = nullptr, SoundCallback = nullptr);
|
||||
Anm2(const std::filesystem::path&);
|
||||
};
|
||||
}
|
||||
|
||||
137
src/resource/dialogue.cpp
Normal file
137
src/resource/dialogue.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#include "dialogue.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "../util/map_.h"
|
||||
#include "../util/xml_.h"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::util;
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
void label_map_query(XMLElement* element, std::map<std::string, int>& labelMap, const char* attribute, int& id)
|
||||
{
|
||||
std::string label{};
|
||||
xml::query_string_attribute(element, attribute, &label);
|
||||
if (auto foundID = map::find(labelMap, label))
|
||||
id = *foundID;
|
||||
else
|
||||
id = -1;
|
||||
}
|
||||
|
||||
Dialogue::Color::Color(XMLElement* element)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Start", &start);
|
||||
element->QueryIntAttribute("End", &end);
|
||||
xml::query_color_attribute(element, "R", &value.r);
|
||||
xml::query_color_attribute(element, "G", &value.g);
|
||||
xml::query_color_attribute(element, "B", &value.b);
|
||||
xml::query_color_attribute(element, "A", &value.a);
|
||||
}
|
||||
|
||||
Dialogue::Animation::Animation(XMLElement* element)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("At", &at);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
}
|
||||
|
||||
Dialogue::Branch::Branch(XMLElement* element, std::map<std::string, int>& labelMap)
|
||||
{
|
||||
if (!element) return;
|
||||
label_map_query(element, labelMap, "Label", nextID);
|
||||
xml::query_string_attribute(element, "Content", &content);
|
||||
}
|
||||
|
||||
Dialogue::Entry::Entry(XMLElement* element, std::map<std::string, int>& labelMap)
|
||||
{
|
||||
if (!element) return;
|
||||
|
||||
xml::query_string_attribute(element, "Content", &content);
|
||||
label_map_query(element, labelMap, "Next", nextID);
|
||||
|
||||
std::string flagString{};
|
||||
xml::query_string_attribute(element, "Flag", &flagString);
|
||||
|
||||
for (int i = 0; i < std::size(FLAG_STRINGS); i++)
|
||||
{
|
||||
if (flagString == FLAG_STRINGS[i])
|
||||
{
|
||||
flag = (Flag)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto child = element->FirstChildElement("Color"); child; child = child->NextSiblingElement("Color"))
|
||||
colors.emplace_back(child);
|
||||
|
||||
for (auto child = element->FirstChildElement("Animation"); child; child = child->NextSiblingElement("Animation"))
|
||||
animations.emplace_back(child);
|
||||
|
||||
for (auto child = element->FirstChildElement("Branch"); child; child = child->NextSiblingElement("Branch"))
|
||||
branches.emplace_back(child, labelMap);
|
||||
}
|
||||
|
||||
Dialogue::Dialogue(const std::string& path)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (document.LoadFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
std::cout << "Failed to initialize dialogue: " << document.ErrorStr() << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
auto element = document.RootElement();
|
||||
int id{};
|
||||
|
||||
for (auto child = element->FirstChildElement("Entry"); child; child = child->NextSiblingElement("Entry"))
|
||||
{
|
||||
std::string label{};
|
||||
xml::query_string_attribute(child, "Label", &label);
|
||||
labelMap.emplace(label, id++);
|
||||
}
|
||||
|
||||
id = 0;
|
||||
|
||||
for (auto child = element->FirstChildElement("Entry"); child; child = child->NextSiblingElement("Entry"))
|
||||
entryMap.emplace(id++, Entry(child, labelMap));
|
||||
|
||||
for (auto& [label, id] : labelMap)
|
||||
{
|
||||
if (label.starts_with(BURP_SMALL_LABEL)) burpSmallIDs.emplace_back(id);
|
||||
if (label.starts_with(BURP_BIG_LABEL)) burpBigIDs.emplace_back(id);
|
||||
if (label.starts_with(EAT_HUNGRY_LABEL)) eatHungryIDs.emplace_back(id);
|
||||
if (label.starts_with(EAT_FULL_LABEL)) eatFullIDs.emplace_back(id);
|
||||
if (label.starts_with(FULL_LABEL)) fullIDs.emplace_back(id);
|
||||
if (label.starts_with(CAPACITY_LOW_LABEL)) capacityLowIDs.emplace_back(id);
|
||||
if (label.starts_with(FEED_HUNGRY_LABEL)) feedHungryIDs.emplace_back(id);
|
||||
if (label.starts_with(FEED_FULL_LABEL)) feedFullIDs.emplace_back(id);
|
||||
if (label.starts_with(FOOD_STOLEN_LABEL)) foodStolenIDs.emplace_back(id);
|
||||
if (label.starts_with(FOOD_EASED_LABEL)) foodEasedIDs.emplace_back(id);
|
||||
if (label.starts_with(PERFECT_LABEL)) perfectIDs.emplace_back(id);
|
||||
if (label.starts_with(MISS_LABEL)) missIDs.emplace_back(id);
|
||||
if (label.starts_with(POST_DIGEST_LABEL)) postDigestIDs.emplace_back(id);
|
||||
if (label.starts_with(RANDOM_LABEL)) randomIDs.emplace_back(id);
|
||||
}
|
||||
|
||||
std::cout << "Initialzed dialogue: " << path << "\n";
|
||||
}
|
||||
|
||||
Dialogue::Entry* Dialogue::get(int id)
|
||||
{
|
||||
if (id == -1) return nullptr;
|
||||
return map::find(entryMap, id);
|
||||
}
|
||||
|
||||
Dialogue::Entry* Dialogue::get(const std::string& label)
|
||||
{
|
||||
auto id = map::find(labelMap, label);
|
||||
if (!id) return nullptr;
|
||||
return get(*id);
|
||||
}
|
||||
|
||||
Dialogue::Entry* Dialogue::next(Dialogue::Entry* entry) { return get(entry->nextID); }
|
||||
}
|
||||
118
src/resource/dialogue.h
Normal file
118
src/resource/dialogue.h
Normal file
@@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
#include "glm/ext/vector_float4.hpp"
|
||||
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
class Dialogue
|
||||
{
|
||||
public:
|
||||
static constexpr auto FULL_LABEL = "Full";
|
||||
static constexpr auto POST_DIGEST_LABEL = "PostDigest";
|
||||
static constexpr auto BURP_SMALL_LABEL = "BurpSmall";
|
||||
static constexpr auto BURP_BIG_LABEL = "BurpBig";
|
||||
static constexpr auto FEED_HUNGRY_LABEL = "FeedHungry";
|
||||
static constexpr auto FEED_FULL_LABEL = "FeedFull";
|
||||
static constexpr auto EAT_HUNGRY_LABEL = "EatHungry";
|
||||
static constexpr auto EAT_FULL_LABEL = "EatFull";
|
||||
static constexpr auto FOOD_STOLEN_LABEL = "FoodStolen";
|
||||
static constexpr auto FOOD_EASED_LABEL = "FoodEased";
|
||||
static constexpr auto CAPACITY_LOW_LABEL = "CapacityLow";
|
||||
static constexpr auto PERFECT_LABEL = "Perfect";
|
||||
static constexpr auto MISS_LABEL = "Miss";
|
||||
static constexpr auto RANDOM_LABEL = "StartRandom";
|
||||
;
|
||||
|
||||
class Color
|
||||
{
|
||||
public:
|
||||
int start{};
|
||||
int end{};
|
||||
glm::vec4 value{};
|
||||
|
||||
Color(tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
class Animation
|
||||
{
|
||||
public:
|
||||
int at{-1};
|
||||
std::string name{};
|
||||
|
||||
Animation(tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
class Branch
|
||||
{
|
||||
public:
|
||||
std::string content{};
|
||||
int nextID{-1};
|
||||
|
||||
Branch(tinyxml2::XMLElement*, std::map<std::string, int>&);
|
||||
};
|
||||
|
||||
class Entry
|
||||
{
|
||||
public:
|
||||
#define FLAGS \
|
||||
X(NONE, "None") \
|
||||
X(ACTIVATE_WINDOWS, "ActivateWindows") \
|
||||
X(DEACTIVATE_WINDOWS, "DeactivateWindows") \
|
||||
X(ONLY_INFO, "OnlyInfo") \
|
||||
X(ACTIVATE_CHEATS, "ActivateCheats")
|
||||
|
||||
enum Flag
|
||||
{
|
||||
#define X(symbol, name) symbol,
|
||||
FLAGS
|
||||
#undef X
|
||||
};
|
||||
|
||||
static constexpr const char* FLAG_STRINGS[] = {
|
||||
#define X(symbol, name) name,
|
||||
FLAGS
|
||||
#undef X
|
||||
};
|
||||
|
||||
#undef FLAGS
|
||||
|
||||
std::string content{};
|
||||
std::vector<Color> colors{};
|
||||
std::vector<Animation> animations{};
|
||||
std::vector<Branch> branches{};
|
||||
int nextID{-1};
|
||||
Flag flag{Flag::NONE};
|
||||
|
||||
Entry(tinyxml2::XMLElement*, std::map<std::string, int>&);
|
||||
};
|
||||
|
||||
std::map<std::string, int> labelMap;
|
||||
std::map<int, Entry> entryMap{};
|
||||
|
||||
std::vector<int> eatHungryIDs{};
|
||||
std::vector<int> eatFullIDs{};
|
||||
std::vector<int> feedHungryIDs{};
|
||||
std::vector<int> feedFullIDs{};
|
||||
std::vector<int> burpSmallIDs{};
|
||||
std::vector<int> burpBigIDs{};
|
||||
std::vector<int> fullIDs{};
|
||||
std::vector<int> foodStolenIDs{};
|
||||
std::vector<int> foodEasedIDs{};
|
||||
std::vector<int> perfectIDs{};
|
||||
std::vector<int> postDigestIDs{};
|
||||
std::vector<int> missIDs{};
|
||||
std::vector<int> capacityLowIDs{};
|
||||
std::vector<int> randomIDs{};
|
||||
|
||||
Dialogue(const std::string&);
|
||||
Entry* get(const std::string&);
|
||||
Entry* get(int = -1);
|
||||
Entry* next(Entry*);
|
||||
};
|
||||
}
|
||||
10
src/resource/font.cpp
Normal file
10
src/resource/font.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "font.h"
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
Font::Font(const std::string& path, float size)
|
||||
{
|
||||
internal = ImGui::GetIO().Fonts->AddFontFromFileTTF(path.c_str(), size);
|
||||
}
|
||||
ImFont* Font::get() { return internal; };
|
||||
}
|
||||
20
src/resource/font.h
Normal file
20
src/resource/font.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "imgui.h"
|
||||
#include <string>
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
class Font
|
||||
{
|
||||
public:
|
||||
ImFont* internal;
|
||||
|
||||
static constexpr auto NORMAL = 12;
|
||||
static constexpr auto BIG = 16;
|
||||
static constexpr auto LARGE = 24;
|
||||
|
||||
Font(const std::string&, float = NORMAL);
|
||||
ImFont* get();
|
||||
};
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace game::resource::shader
|
||||
};
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
constexpr auto VERTEX = R"(#version 300 es
|
||||
inline constexpr auto VERTEX = R"(#version 300 es
|
||||
layout (location = 0) in vec2 i_position;
|
||||
layout (location = 1) in vec2 i_uv;
|
||||
out vec2 v_uv;
|
||||
@@ -30,7 +30,17 @@ namespace game::resource::shader
|
||||
}
|
||||
)";
|
||||
|
||||
constexpr auto FRAGMENT = R"(#version 300 es
|
||||
inline constexpr auto FRAGMENT = R"(#version 300 es
|
||||
precision mediump float;
|
||||
uniform vec4 u_color;
|
||||
out vec4 o_fragColor;
|
||||
void main()
|
||||
{
|
||||
o_fragColor = u_color;
|
||||
}
|
||||
)";
|
||||
|
||||
inline constexpr auto TEXTURE_FRAGMENT = R"(#version 300 es
|
||||
precision mediump float;
|
||||
in vec2 v_uv;
|
||||
uniform sampler2D u_texture;
|
||||
@@ -46,7 +56,7 @@ namespace game::resource::shader
|
||||
}
|
||||
)";
|
||||
#else
|
||||
constexpr auto VERTEX = R"(#version 330 core
|
||||
inline constexpr auto VERTEX = R"(#version 330 core
|
||||
layout (location = 0) in vec2 i_position;
|
||||
layout (location = 1) in vec2 i_uv;
|
||||
out vec2 v_uv;
|
||||
@@ -61,7 +71,17 @@ namespace game::resource::shader
|
||||
}
|
||||
)";
|
||||
|
||||
constexpr auto FRAGMENT = R"(#version 330 core
|
||||
inline constexpr auto FRAGMENT = R"(
|
||||
#version 330 core
|
||||
out vec4 o_fragColor;
|
||||
uniform vec4 u_color;
|
||||
void main()
|
||||
{
|
||||
o_fragColor = u_color;
|
||||
}
|
||||
)";
|
||||
|
||||
inline constexpr auto TEXTURE_FRAGMENT = R"(#version 330 core
|
||||
in vec2 v_uv;
|
||||
uniform sampler2D u_texture;
|
||||
uniform vec4 u_tint;
|
||||
@@ -75,6 +95,7 @@ namespace game::resource::shader
|
||||
o_fragColor = texColor;
|
||||
}
|
||||
)";
|
||||
|
||||
#endif
|
||||
|
||||
constexpr auto UNIFORM_MODEL = "u_model";
|
||||
@@ -82,9 +103,12 @@ namespace game::resource::shader
|
||||
constexpr auto UNIFORM_PROJECTION = "u_projection";
|
||||
constexpr auto UNIFORM_TEXTURE = "u_texture";
|
||||
constexpr auto UNIFORM_TINT = "u_tint";
|
||||
constexpr auto UNIFORM_COLOR = "u_color";
|
||||
constexpr auto UNIFORM_COLOR_OFFSET = "u_color_offset";
|
||||
|
||||
#define SHADERS X(TEXTURE, VERTEX, FRAGMENT)
|
||||
#define SHADERS \
|
||||
X(TEXTURE, VERTEX, TEXTURE_FRAGMENT) \
|
||||
X(RECT, VERTEX, FRAGMENT)
|
||||
|
||||
enum Type
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user