Skip to content

Extensions (RGSS, Modules)

Splendide Imaginarius edited this page Aug 1, 2024 · 9 revisions

Kernel

.load_data(path: String[, restore: Boolean = true]): Now possesses a second optional Boolean argument. If restore is false, load_data will not try to load the file data with Marshal first.

# This will not work, since load_data originally only
# loaded Marshal-dumped data...
stuff = load_data("regular_data/sometext.txt")

# ...but this would work, returning an ASCII-8BIT encoded
# string (or just a string, if built with a version of MRI
# before encodings)
stuff = load_data("regular_data/sometext.txt", false)

This enables you to store any kind of data you want within archives or paths loaded with System.mount.

Audio

mkxp-z can play multiple BGM tracks at once. to enable this functionality, just raise the BGMTrackCount setting in the JSON configuration to however many tracks you think you'll need.

Each BGM function now has an additional optional argument that indicates which track you want it to operate upon. This value is zero-indexed, compared to the one-indexed value in the JSON, so if you ask for 4 tracks, you can use 0-3 as track values.

When no track is given (nil or nothing provided), track 0 will be assumed. bgm_play, bgm_stop, and bgm_fade will automatically stop/fade all other currently playing tracks in this case. If a track is given, on the other hand, these functions will operate on that track specifically, not affecting any others.

Two new functions were added to control BGM volume at any point in time, as well:

  • .bgm_volume([track: Integer|Nil]) returns current volume levels, in values from 0 to 100.
  • .bgm_set_volume(vol: Integer[, track: Integer|Nil]) sets current volume levels, using a value from 0 to 100.

If a track is provided, then these functions use values specific to the given channel. Otherwise, these two functions use a multiplier that is applied across every track. This way, you can either control the entire BGM "bus" as a whole, or adjust specific sections of the BGM that you wish to be audible.

BGM playback supports FLAC.

Input

Detecting Key States

mkxp-z has four new functions that mirror the normal input functions: .pressex?, .triggerex?, .repeatex?, and .releaseex?. These can be used in two ways:

  1. By using Windows Virtual-Key codes, or
  2. the names of SDL scancodes, as symbols (minus the SDL_SCANCODE_ prefix).
# Microsoft VKey-codes
Input.pressex? 0x01 # Left mouse button
Input.triggerex? 0xa1 # Left Shift

# SDL Scancode names, without their "SDL_" prefix...
Input.repeatex? :BACKSPACE
Input.triggerex? :RETURN

# ...except for top row number keys! They are prefixed with `NUMBER_`
Input.releaseex? :NUMBER_7

You may also check the amount of time a key has been pressed using .time? and .timeex? for RGSS inputs and raw inputs, respectively. Time is measured in microseconds.

Getting Text Input

If the .text_input property is set to true, mkxp-z will begin receiving proper text input events, so that you can get direct strings from what is inputted through the keyboard. Setting it to false will disable it again. While accepting text input, calling .gets will return the text that has been entered since the last time .gets was called (using it will clear the buffer)

You may also use the .clipboard property to get and set the user's clipboard.

An example of both of these concepts:

# A buffer to store the text in
text = ""

# Turn on text input
Input.text_input = true

# Wait until Enter gets pressed
until Input.pressex?(:RETURN)

    # Check for Ctrl+C and Ctrl+V!
    if Input.pressex?(:LCTRL) || Input.pressex?(:RCTRL)
        Input.clipboard = text if Input.triggerex?(:C)
        # << is faster than +=
        text << Input.clipboard if Input.triggerex?(:V)
    else
        text << Input.gets
    end
    
    # Next frame
    Input.update
    Graphics.update
end

# Disable text input and print the text that got typed in
Input.text_input = false
p text

If you really need it, you can obtain the state of all keys at the time of the last Input.update by using .raw_key_states. It returns an array of Booleans for every SDL scancode, in order.

Mouse

Mouse button states can be checked using "standard" RGSS input codes:

Input.press? Input::MOUSELEFT
Input.press? Input::MOUSEMIDDLE
Input.press? Input::MOUSERIGHT
Input.press? Input::MOUSEX1 # Mouse Button 4
Input.press? Input::MOUSEX2 # Mouse Button 5
  • Vertical scroll can be polled by using .scroll_v.
  • Visibility of the cursor can be toggled with the Graphics.show_cursor property. A bit unintuitive that it's not in the Input module I think, but it's there in the original version of mkxp and I chose not to change it.
  • The position of the mouse, as far as the game is concerned, can be polled with .mouse_x and .mouse_y.
  • Whether or not the mouse is currently within the window can be checked using .mouse_in_window?.

Controllers

There is now an Input::Controller module that you can use to interact with controllers. It has a bunch of the same methods as the regular Input module, plus some extras for reading sticks, triggers, and other information.

Within the context of this module:

  • .connected? returns whether or not there is a controller connected in the first place.
  • name returns the connected controller's name.
  • .pressex?, .triggerex?, .repeatex? and .releaseex? are just like the parent module's functions of the same name. Here though, they take the names of SDL_GameControllerButtons, without the SDL_CONTROLLER_BUTTON_ prefix. The integer values also work.
  • .power_level, returns the controller's power level as a symbol. It can be anything from :MAX, :WIRED, :HIGH, :MEDIUM, :LOW, or :EMPTY. It might also just not work, in which case you'll get :UNKNOWN.
  • .axes_left returns an array containing the X and Y values of the left stick. .axes_right does the same for the right stick. The values are floats that can range from -1 to 1. (-1 is farthest to the left/top)
  • .axes_trigger returns an array containing the values of the left and right trigger. The values are floats that can range from 0 to 1. (0 is not pressed at all)
  • .raw_button_states, like Input.raw_key_states, returns an array of booleans, one for each button in the same order listed by SDL.
  • .raw_axes does a similar thing for the axes, in this order.
Input::Controller.connected? # There's a controller connected in the first place, right?
Input::Controller.pressex? :START # Is the start button pressed?
ls_x, ls_y = Input::Controller.axes_left # What is the position of the left stick?

Graphics

  • You may automatically resize the window based on the internal width and height by using the .scale/.scale= property. It accepts values between 0.5 and 4 (but you should only ever need up to 2).
  • For more fine control, you can use .resize_window(width: Integer, height: Integer[, center: Boolean = false]). This will resize the window to any size you want. If center is set to true, the window will be centered on the screen as well.
  • Set and unset fullscreen state by setting .fullscreen/.fullscreen= to true or false.
  • .center will move the window to the center of the screen.
  • Important enough to mention: Because most VX Ace functions are now available to XP and VX modes by default, you can use .resize_screen to change the internal screen size and available drawing area.
  • .resize_screen doesn't impose an upper limit of 640x480 like Enterbrain's runtime does; you can pick as high a resolution as your GPU can handle.
  • .frame_rate= doesn't impose a range of 10 to 120 like Enterbrain's runtime does; you can pick as high a frame rate as your CPU and GPU can handle, or as low as 1 FPS.
  • Certain settings can be adjusted by setting these properties to true or false:
    • .fixed_aspect_ratio/.fixed_aspect_ratio=
    • .smooth_scaling/.smooth_scaling=
    • .integer_scaling/.integer_scaling=
    • .last_mile_scaling/.last_mile_scaling=

Graphics.play_movie

mkxp-z had support for video playback contributed a while back. The function is back, and available on all RGSS versions as with most of the other version-exclusive APIs.

The function has a couple new additions, and a couple of caveats.

The pros: The function's "signature" now looks a bit like this:

#                   file to play       volume            scene is skippable?
Graphics.play_movie(filename: String[, volume: Integer[, skip: Boolean = false]])

You may set volume with volume, and whether or not the scene is allowed to be skipped with skip. If skipping is allowed, pressing Input::B or Input::C will immediately end video playback.

Videos are scaled to fit the game's screen (not the window), with a black letterbox placed behind them when aspect ratios do not match. If the letterbox isn't desired, both the letterbox and the video are just normal Sprites backed by bitmaps; the letterbox has a z-level of 4999, and the video has a z-level of 5001. This means that you can create a new sprite with a z-level of 5000 and it will appear between the letterbox and the video, effectively letting you replace or "remove" the letterbox.

The cons:

You must use Ogg-Theora (.ogv) video with a YUV420p pixel format. If the video isn't OGV, the function will fail. If the video is OGV but not YUV420p, the program will panic and abort (just something that Theoraplay does, apparently).

With FFMPEG, you'd make something in the correct format like this:

ffmpeg -i in.mp4 -b:a 128K -q:v 7 -pix_fmt yuv420p out.ogv
# -q:v     - video quality, higher is "better" for theora
#            you can also use -b:v to set video bitrate directly
# -pix_fmt - pixel format
# -b:a     - audio bitrate

The video processing isn't hardware-accelerated. Theoraplay converts each frame from YUV420p to RGB through CPU, then that gets uploaded to a bitmap and displayed. Basically: be careful with your video resolution and bitrate. If it's too high, it will lag and cause strange things to happen.

The video must use "normal" dimensions. This means any combination of the 240s, 360s, 480s, 720s... no 853s.

In general though, this is still better than not having it at all. Using it functions just like this:

# Just play a video, unskippable, volume 100
Graphics.play_movie("cutscene.ogv")

# Play a video, volume 60%, allow skipping
Graphics.play_movie("cutscene.ogv", 60, true)

Screenshots

.screenshot(path: String) will take a screenshot and save it to path in PNG, JPEG, or BMP format depending on the file extension given.

You may also use Graphics.snap_to_bitmap.to_file(path: String). They do the same thing.

Telling Time

.delta returns the amount of time passed since the last call to Graphics.update. Time is, as usual, in seconds, so if you are running at 60FPS then you should expect a value around 0.016.

You can get the "real" current framerate with Graphics.average_frame_rate. This is measured based on how long it took the past several calls to Graphics.update to come through, including running all of your code, so it's more accurate in general than normal mkxp's FPS counter. It's the same number you see when you press F2 or on your Touch Bar, only as a Float, and unrounded.