scene transitions via Scene object
This commit is contained in:
parent
40c0eb2693
commit
fc95fc2844
6 changed files with 322 additions and 35 deletions
|
|
@ -9,6 +9,7 @@
|
|||
#include "PyWindow.h"
|
||||
#include "PySceneObject.h"
|
||||
#include "PyFOV.h"
|
||||
#include "PyTransition.h"
|
||||
#include "PySound.h"
|
||||
#include "PyMusic.h"
|
||||
#include "PyKeyboard.h"
|
||||
|
|
@ -51,6 +52,14 @@ static PyObject* mcrfpy_module_getattr(PyObject* self, PyObject* args)
|
|||
return McRFPy_API::api_get_scenes();
|
||||
}
|
||||
|
||||
if (strcmp(name, "default_transition") == 0) {
|
||||
return PyTransition::to_python(PyTransition::default_transition);
|
||||
}
|
||||
|
||||
if (strcmp(name, "default_transition_duration") == 0) {
|
||||
return PyFloat_FromDouble(PyTransition::default_duration);
|
||||
}
|
||||
|
||||
// Attribute not found - raise AttributeError
|
||||
PyErr_Format(PyExc_AttributeError, "module 'mcrfpy' has no attribute '%s'", name);
|
||||
return NULL;
|
||||
|
|
@ -71,6 +80,33 @@ static int mcrfpy_module_setattro(PyObject* self, PyObject* name, PyObject* valu
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (strcmp(name_str, "default_transition") == 0) {
|
||||
TransitionType trans;
|
||||
if (!PyTransition::from_arg(value, &trans, nullptr)) {
|
||||
return -1;
|
||||
}
|
||||
PyTransition::default_transition = trans;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(name_str, "default_transition_duration") == 0) {
|
||||
double duration;
|
||||
if (PyFloat_Check(value)) {
|
||||
duration = PyFloat_AsDouble(value);
|
||||
} else if (PyLong_Check(value)) {
|
||||
duration = PyLong_AsDouble(value);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "default_transition_duration must be a number");
|
||||
return -1;
|
||||
}
|
||||
if (duration < 0.0) {
|
||||
PyErr_SetString(PyExc_ValueError, "default_transition_duration must be non-negative");
|
||||
return -1;
|
||||
}
|
||||
PyTransition::default_duration = static_cast<float>(duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// For other attributes, use default module setattr
|
||||
return PyObject_GenericSetAttr(self, name, value);
|
||||
}
|
||||
|
|
@ -393,6 +429,15 @@ PyObject* PyInit_mcrfpy()
|
|||
PyModule_AddIntConstant(m, "default_fov", FOV_BASIC);
|
||||
}
|
||||
|
||||
// Add Transition enum class (uses Python's IntEnum)
|
||||
PyObject* transition_class = PyTransition::create_enum_class(m);
|
||||
if (!transition_class) {
|
||||
// If enum creation fails, continue without it (non-fatal)
|
||||
PyErr_Clear();
|
||||
}
|
||||
// Note: default_transition and default_transition_duration are handled via
|
||||
// mcrfpy_module_getattr/setattro using PyTransition::default_transition/default_duration
|
||||
|
||||
// Add automation submodule
|
||||
PyObject* automation_module = McRFPy_Automation::init_automation_module();
|
||||
if (automation_module != NULL) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "GameEngine.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include "McRFPy_Doc.h"
|
||||
#include "PyTransition.h"
|
||||
#include <iostream>
|
||||
|
||||
// Static map to store Python scene objects by name
|
||||
|
|
@ -75,13 +76,54 @@ PyObject* PySceneClass::__repr__(PySceneObject* self)
|
|||
return PyUnicode_FromFormat("<Scene '%s'>", self->name.c_str());
|
||||
}
|
||||
|
||||
PyObject* PySceneClass::activate(PySceneObject* self, PyObject* args)
|
||||
PyObject* PySceneClass::activate(PySceneObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
// Call the static method from McRFPy_API
|
||||
PyObject* py_args = Py_BuildValue("(s)", self->name.c_str());
|
||||
PyObject* result = McRFPy_API::_setScene(NULL, py_args);
|
||||
Py_DECREF(py_args);
|
||||
return result;
|
||||
static const char* keywords[] = {"transition", "duration", nullptr};
|
||||
PyObject* transition_arg = nullptr;
|
||||
PyObject* duration_arg = nullptr;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", const_cast<char**>(keywords),
|
||||
&transition_arg, &duration_arg)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get transition type (use default if not provided)
|
||||
TransitionType transition_type;
|
||||
bool trans_was_none = false;
|
||||
if (transition_arg) {
|
||||
if (!PyTransition::from_arg(transition_arg, &transition_type, &trans_was_none)) {
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
transition_type = PyTransition::default_transition;
|
||||
}
|
||||
|
||||
// Get duration (use default if not provided)
|
||||
float duration;
|
||||
if (duration_arg && duration_arg != Py_None) {
|
||||
if (PyFloat_Check(duration_arg)) {
|
||||
duration = static_cast<float>(PyFloat_AsDouble(duration_arg));
|
||||
} else if (PyLong_Check(duration_arg)) {
|
||||
duration = static_cast<float>(PyLong_AsLong(duration_arg));
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "duration must be a number");
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
duration = PyTransition::default_duration;
|
||||
}
|
||||
|
||||
// Build transition string for _setScene (or call game->changeScene directly)
|
||||
GameEngine* game = McRFPy_API::game;
|
||||
if (!game) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "No game engine");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Call game->changeScene directly with proper transition
|
||||
game->changeScene(self->name, transition_type, duration);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
// children property getter (replaces get_ui method)
|
||||
|
|
@ -455,12 +497,15 @@ PyGetSetDef PySceneClass::getsetters[] = {
|
|||
|
||||
// Methods
|
||||
PyMethodDef PySceneClass::methods[] = {
|
||||
{"activate", (PyCFunction)activate, METH_NOARGS,
|
||||
{"activate", (PyCFunction)activate, METH_VARARGS | METH_KEYWORDS,
|
||||
MCRF_METHOD(SceneClass, activate,
|
||||
MCRF_SIG("()", "None"),
|
||||
MCRF_DESC("Make this the active scene."),
|
||||
MCRF_SIG("(transition: Transition = None, duration: float = None)", "None"),
|
||||
MCRF_DESC("Make this the active scene with optional transition effect."),
|
||||
MCRF_ARGS_START
|
||||
MCRF_ARG("transition", "Transition type (mcrfpy.Transition enum). Defaults to mcrfpy.default_transition")
|
||||
MCRF_ARG("duration", "Transition duration in seconds. Defaults to mcrfpy.default_transition_duration")
|
||||
MCRF_RETURNS("None")
|
||||
MCRF_NOTE("Deactivates the current scene and activates this one. Scene transitions and lifecycle callbacks are triggered.")
|
||||
MCRF_NOTE("Deactivates the current scene and activates this one. Lifecycle callbacks (on_exit, on_enter) are triggered.")
|
||||
)},
|
||||
{"register_keyboard", (PyCFunction)register_keyboard, METH_VARARGS,
|
||||
MCRF_METHOD(SceneClass, register_keyboard,
|
||||
|
|
@ -575,9 +620,8 @@ int McRFPy_API::api_set_current_scene(PyObject* value)
|
|||
return -1;
|
||||
}
|
||||
|
||||
std::string old_scene = game->scene;
|
||||
game->scene = scene_name;
|
||||
McRFPy_API::triggerSceneChange(old_scene, scene_name);
|
||||
// Use changeScene with default transition settings
|
||||
game->changeScene(scene_name, PyTransition::default_transition, PyTransition::default_duration);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public:
|
|||
static PyObject* __repr__(PySceneObject* self);
|
||||
|
||||
// Scene methods
|
||||
static PyObject* activate(PySceneObject* self, PyObject* args);
|
||||
static PyObject* activate(PySceneObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* register_keyboard(PySceneObject* self, PyObject* args);
|
||||
|
||||
// Properties
|
||||
|
|
|
|||
158
src/PyTransition.cpp
Normal file
158
src/PyTransition.cpp
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
#include "PyTransition.h"
|
||||
#include "McRFPy_API.h"
|
||||
|
||||
// Static storage
|
||||
PyObject* PyTransition::transition_enum_class = nullptr;
|
||||
TransitionType PyTransition::default_transition = TransitionType::None;
|
||||
float PyTransition::default_duration = 1.0f;
|
||||
|
||||
PyObject* PyTransition::create_enum_class(PyObject* module) {
|
||||
// Import IntEnum from enum module
|
||||
PyObject* enum_module = PyImport_ImportModule("enum");
|
||||
if (!enum_module) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject* int_enum = PyObject_GetAttrString(enum_module, "IntEnum");
|
||||
Py_DECREF(enum_module);
|
||||
if (!int_enum) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create dict of enum members
|
||||
PyObject* members = PyDict_New();
|
||||
if (!members) {
|
||||
Py_DECREF(int_enum);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add all Transition type members
|
||||
// Values match the C++ TransitionType enum
|
||||
struct {
|
||||
const char* name;
|
||||
int value;
|
||||
} transition_members[] = {
|
||||
{"NONE", static_cast<int>(TransitionType::None)},
|
||||
{"FADE", static_cast<int>(TransitionType::Fade)},
|
||||
{"SLIDE_LEFT", static_cast<int>(TransitionType::SlideLeft)},
|
||||
{"SLIDE_RIGHT", static_cast<int>(TransitionType::SlideRight)},
|
||||
{"SLIDE_UP", static_cast<int>(TransitionType::SlideUp)},
|
||||
{"SLIDE_DOWN", static_cast<int>(TransitionType::SlideDown)},
|
||||
};
|
||||
|
||||
for (const auto& m : transition_members) {
|
||||
PyObject* value = PyLong_FromLong(m.value);
|
||||
if (!value) {
|
||||
Py_DECREF(members);
|
||||
Py_DECREF(int_enum);
|
||||
return NULL;
|
||||
}
|
||||
if (PyDict_SetItemString(members, m.name, value) < 0) {
|
||||
Py_DECREF(value);
|
||||
Py_DECREF(members);
|
||||
Py_DECREF(int_enum);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(value);
|
||||
}
|
||||
|
||||
// Call IntEnum("Transition", members) to create the enum class
|
||||
PyObject* name = PyUnicode_FromString("Transition");
|
||||
if (!name) {
|
||||
Py_DECREF(members);
|
||||
Py_DECREF(int_enum);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// IntEnum(name, members) using functional API
|
||||
PyObject* args = PyTuple_Pack(2, name, members);
|
||||
Py_DECREF(name);
|
||||
Py_DECREF(members);
|
||||
if (!args) {
|
||||
Py_DECREF(int_enum);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject* transition_class = PyObject_Call(int_enum, args, NULL);
|
||||
Py_DECREF(args);
|
||||
Py_DECREF(int_enum);
|
||||
|
||||
if (!transition_class) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Cache the reference for fast type checking
|
||||
transition_enum_class = transition_class;
|
||||
Py_INCREF(transition_enum_class);
|
||||
|
||||
// Add to module
|
||||
if (PyModule_AddObject(module, "Transition", transition_class) < 0) {
|
||||
Py_DECREF(transition_class);
|
||||
transition_enum_class = nullptr;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return transition_class;
|
||||
}
|
||||
|
||||
int PyTransition::from_arg(PyObject* arg, TransitionType* out_type, bool* was_none) {
|
||||
if (was_none) *was_none = false;
|
||||
|
||||
// Accept None -> caller should use default
|
||||
if (arg == Py_None || arg == NULL) {
|
||||
if (was_none) *was_none = true;
|
||||
*out_type = default_transition;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Accept Transition enum member (check if it's an instance of our enum)
|
||||
if (transition_enum_class && PyObject_IsInstance(arg, transition_enum_class)) {
|
||||
// IntEnum members have a 'value' attribute
|
||||
PyObject* value = PyObject_GetAttrString(arg, "value");
|
||||
if (!value) {
|
||||
return 0;
|
||||
}
|
||||
long val = PyLong_AsLong(value);
|
||||
Py_DECREF(value);
|
||||
if (val == -1 && PyErr_Occurred()) {
|
||||
return 0;
|
||||
}
|
||||
*out_type = static_cast<TransitionType>(val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Accept int (for flexibility)
|
||||
if (PyLong_Check(arg)) {
|
||||
long val = PyLong_AsLong(arg);
|
||||
if (val == -1 && PyErr_Occurred()) {
|
||||
return 0;
|
||||
}
|
||||
if (val < 0 || val > static_cast<int>(TransitionType::SlideDown)) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"Invalid Transition value: %ld. Must be 0-5 or use mcrfpy.Transition enum.",
|
||||
val);
|
||||
return 0;
|
||||
}
|
||||
*out_type = static_cast<TransitionType>(val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"transition must be mcrfpy.Transition enum member, int, or None");
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* PyTransition::to_python(TransitionType type) {
|
||||
if (!transition_enum_class) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Transition enum not initialized");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get the enum member by value
|
||||
PyObject* value = PyLong_FromLong(static_cast<int>(type));
|
||||
if (!value) return NULL;
|
||||
|
||||
PyObject* result = PyObject_CallFunctionObjArgs(transition_enum_class, value, NULL);
|
||||
Py_DECREF(value);
|
||||
return result;
|
||||
}
|
||||
29
src/PyTransition.h
Normal file
29
src/PyTransition.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Python.h"
|
||||
#include "SceneTransition.h"
|
||||
|
||||
// Module-level Transition enum class (created at runtime using Python's IntEnum)
|
||||
// Stored as a module attribute: mcrfpy.Transition
|
||||
|
||||
class PyTransition {
|
||||
public:
|
||||
// Create the Transition enum class and add to module
|
||||
// Returns the enum class (new reference), or NULL on error
|
||||
static PyObject* create_enum_class(PyObject* module);
|
||||
|
||||
// Helper to extract transition type from Python arg (accepts Transition enum, int, or None)
|
||||
// Returns 1 on success, 0 on error (with exception set)
|
||||
// If arg is None, sets *out_type to the default and sets *was_none to true
|
||||
static int from_arg(PyObject* arg, TransitionType* out_type, bool* was_none = nullptr);
|
||||
|
||||
// Convert TransitionType to Python enum member
|
||||
static PyObject* to_python(TransitionType type);
|
||||
|
||||
// Cached reference to the Transition enum class for fast type checking
|
||||
static PyObject* transition_enum_class;
|
||||
|
||||
// Module-level defaults
|
||||
static TransitionType default_transition;
|
||||
static float default_duration;
|
||||
};
|
||||
|
|
@ -63,7 +63,7 @@ def create_test_scenes():
|
|||
print("Created test scenes: red_scene, blue_scene, green_scene, menu_scene")
|
||||
|
||||
# Track current transition type
|
||||
current_transition = "fade"
|
||||
current_transition = mcrfpy.Transition.FADE
|
||||
transition_duration = 1.0
|
||||
|
||||
def handle_key(key, action):
|
||||
|
|
@ -76,24 +76,35 @@ def handle_key(key, action):
|
|||
current_scene = (mcrfpy.current_scene.name if mcrfpy.current_scene else None)
|
||||
|
||||
# Number keys set transition type
|
||||
if key == "Num1":
|
||||
current_transition = "fade"
|
||||
print("Transition set to: fade")
|
||||
elif key == "Num2":
|
||||
current_transition = "slide_left"
|
||||
print("Transition set to: slide_left")
|
||||
elif key == "Num3":
|
||||
current_transition = "slide_right"
|
||||
print("Transition set to: slide_right")
|
||||
elif key == "Num4":
|
||||
current_transition = "slide_up"
|
||||
print("Transition set to: slide_up")
|
||||
elif key == "Num5":
|
||||
current_transition = "slide_down"
|
||||
print("Transition set to: slide_down")
|
||||
elif key == "Num6":
|
||||
current_transition = None # Instant
|
||||
print("Transition set to: instant")
|
||||
keyselections = {
|
||||
"Num1": mcrfpy.Transition.FADE,
|
||||
"Num2": mcrfpy.Transition.SLIDE_LEFT,
|
||||
"Num3": mcrfpy.Transition.SLIDE_RIGHT,
|
||||
"Num4": mcrfpy.Transition.SLIDE_UP,
|
||||
"Num5": mcrfpy.Transition.SLIDE_DOWN,
|
||||
"Num6": mcrfpy.Transition.NONE
|
||||
}
|
||||
if key in keyselections:
|
||||
current_transition = keyselections[key]
|
||||
print(f"Transition set to: {current_transition}")
|
||||
#if key == "Num1":
|
||||
# current_transition = "fade"
|
||||
# print("Transition set to: fade")
|
||||
#elif key == "Num2":
|
||||
# current_transition = "slide_left"
|
||||
# print("Transition set to: slide_left")
|
||||
#elif key == "Num3":
|
||||
# current_transition = "slide_right"
|
||||
# print("Transition set to: slide_right")
|
||||
#elif key == "Num4":
|
||||
# current_transition = "slide_up"
|
||||
# print("Transition set to: slide_up")
|
||||
#elif key == "Num5":
|
||||
# current_transition = "slide_down"
|
||||
# print("Transition set to: slide_down")
|
||||
#elif key == "Num6":
|
||||
# current_transition = None # Instant
|
||||
# print("Transition set to: instant")
|
||||
|
||||
# Letter keys change scene
|
||||
keytransitions = {
|
||||
|
|
@ -104,7 +115,7 @@ def handle_key(key, action):
|
|||
}
|
||||
if key in keytransitions:
|
||||
if mcrfpy.current_scene != keytransitions[key]:
|
||||
keytransitions[key].activate()
|
||||
keytransitions[key].activate(current_transition, transition_duration)
|
||||
#elif key == "R":
|
||||
# if current_scene != "red_scene":
|
||||
# print(f"Transitioning to red_scene with {current_transition}")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue