Custom rewrite, url path with parameters to a page

Your code seems to work fine with slight modifications. Taken from here:
https://developer.wordpress.org/reference/functions/add_rewrite_rule/#comment-content-4787

First off, I moved the rewrite rules to init and modified the regex to match parameters optionally:

function wse_414800_rewrite_state_city() {

    add_rewrite_rule(
        '^(pluginpage)/([^/]*)/([^/]*)/?',
        'index.php?pagename=pluginpage&state=$matches[2]&city=$matches[3]',
        'top'
    );

    add_rewrite_rule(
        '^(pluginpage)/([^/]*)/?',
        'index.php?pagename=pluginpage&state=$matches[2]',
        'top'
    );
}
add_action('init', 'wse_414800_rewrite_state_city');

And, to be able to retrieve our custom query params I’ve added a function to register custom tags.

// Register query parameters for state and city
function wse_414800_register_query_param() {
    add_rewrite_tag('%state%', '([^&]+)');
    add_rewrite_tag('%city%', '([^&]+)');
}
add_action('init', 'wse_414800_register_query_param' );

And later on, you can retrieve the parameter either in your shortcode or in a page template using get_query_var()

For your query: “Or might there be a better approach by mapping the URLs directly to the plugin without using a page and a shortcode”

You can use a Page template as an alternative, here is an example for registering and using page template: https://developer.wordpress.org/reference/functions/add_rewrite_rule/#comment-content-4554)

Further it depends on what type of plugin you’re building, if it’s for internal use you can choose either of the options shortcode or template, but if you plan to publish the plugin IMHO a Gutenberg block or shortcode would do better.

function wse_414800_show_state_city_cb( $atts ) {

    $state = get_query_var('state');
    $city = get_query_var('city');

    return "State: " . esc_html( $state ) .", City: " .esc_html( $city );
}

add_shortcode( 'show_state_city', 'wse_414800_show_state_city_cb' );

You could utilize the plugin like Query Monitor to check what/how the rewrite rule is working.

Query Parameter - Rewrite rule match