Choose between browser and server actions
Source:vignettes/browser-server-actions.Rmd
browser-server-actions.RmdEvent handlers in shinyphaser can do work in two different places:
-
browser_actionruns immediately in the player’s browser; and -
server_actionruns as ordinary R code in the Shiny server session.
You can use either one or both. The right choice depends on whether the work needs low latency, arbitrary R code, or authoritative server state.
Quick decision guide
| Use | Best for | Important constraint |
|---|---|---|
browser_action |
Animations, sound, movement, text changes, and other immediate feedback | Only supported shinyphaser methods and browser-action conditions can be used |
server_action |
Reactive state, databases, files, R packages, validation, and game rules managed by R | Requires a browser-to-server round trip |
| Both | Immediate feedback followed by authoritative R processing | The browser action runs without waiting for the server action |
A useful rule is: show feedback in the browser, but make important decisions on the server.
Browser actions
Declare browser work with the browser_actions()
constructor. It captures the calls without running them in R and
compiles them into commands understood by the JavaScript game
runtime.
game$add_control(
key = "Space",
browser_action = browser_actions(
jump_sound$play(),
hero$set_velocity_y(-600),
hero$play_animation("hero_jump", duration = 250)
)
)This is appropriate for a jump because the player should see and hear it as soon as the key is pressed. No Shiny input event is emitted when an event has only a browser action.
browser_actions() is not a general R expression
evaluator. For example, it cannot update a reactiveVal(),
query a database, read a file, or call an arbitrary R helper:
# Not valid browser work
game$add_control(
key = "Space",
browser_action = browser_actions(
score(score() + 1),
save_score_to_database()
)
)Move such work to server_action instead. Unsupported
browser calls fail when the handler is registered rather than silently
falling back to the server.
Supported browser expressions
The following table is the complete browser-action surface. Methods
not listed here are ordinary server-side methods and cannot be placed in
browser_actions().
| Object | Supported methods inside browser_actions()
|
|---|---|
Text |
show(), hide(), set()
|
Image, Rectangle
|
show(), hide()
|
Sound |
play(), pause(), resume(),
stop()
|
Sprite |
show(), hide(),
play_animation(), set_in_motion(),
set_velocity_x(), set_velocity_y(),
stop_motion(), add_player_controls(),
destroy()
|
StaticSprite |
show(), hide(),
destroy()
|
Group |
show(), hide()
|
StaticGroup |
show(), hide(),
disable()
|
Browser state returned by game$add_state()
|
set(), increment(),
decrement(), add(),
subtract()
|
Browser cooldown returned by game$add_cooldown()
|
trigger() |
PhaserGame |
alert(), emit(), after()
|
An if block may combine &&,
||, !, and the comparison operators
==, !=, <, <=,
>, and >=. Conditions may call
overlaps() or exists() on scene objects,
is_true() or is_false() on browser state, and
ready() on a browser cooldown. These condition and
state/cooldown methods are a small R-like language captured by
browser_actions(); do not call them as ordinary R methods
outside an action block.
Literal values, local scalar values, and named arguments are
supported. Code that calls any other function, mutates an R object, or
depends on a reactive value belongs in server_action.
Server actions
A server action is a function that receives the event sent by the
browser. It can contain ordinary R code and use the rest of the Shiny
server environment. Pass the Shiny input object whenever
you register one.
score <- shiny::reactiveVal(0)
game$add_overlap(
object_one = "hero",
object_two = "coin",
input = input,
server_action = function(event) {
score(score() + 1)
saveRDS(score(), "score.rds")
}
)Supplying server_action automatically enables the
corresponding browser-to- server event. You do not need to create a
separate observeEvent() for the generated input ID.
The event payload depends on the handler. An overlap event can include object coordinates, while a keyboard event includes its key code. Name the argument even when a callback does not need its value:
game$add_control(
key = "Space",
input = input,
server_action = function(event) {
showNotification(paste("Pressed", event$code))
}
)Use both for responsive, server-authoritative behavior
Some interactions need immediate feedback and server processing. Supply both parameters on the same handler:
game$add_overlap(
object_one = "hero",
object_two = "wizard",
input = input,
browser_action = browser_actions(
talk_prompt$show(),
wizard$play_animation("wizard_talk"),
greeting_sound$play()
),
server_action = function(event) {
visits_to_wizard(visits_to_wizard() + 1)
load_dialogue_from_database()
}
)The browser action runs immediately. The server action runs after Shiny receives the event; it does not block the browser action.
Avoid making the two sides independently change the same authoritative state. For example, the browser may play an attack animation immediately, but the server should decide whether the attack hit and how much damage it caused. After that decision, use normal shinyphaser methods to send the result back to the browser.
Example: a server-managed Space interaction
The same key may mean different things in a larger game: pick up an item, talk to a character, or attack an enemy. If those conditions live in R, keep the decision in one server action:
handle_space <- function(event) {
if (sword_in_range && !has_sword) {
has_sword <<- TRUE
sword$destroy()
inventory_text$set("weapon: sword")
return(invisible(NULL))
}
if (wizard_in_range) {
shinyalert::shinyalert("The wizard has a quest for you.")
return(invisible(NULL))
}
attack_enemy_in_range()
}
game$add_control(
key = "Space",
input = input,
server_action = handle_space
)There is no browser action here because the server must first determine what Space means. A browser action can be added later if the required interaction state is also maintained safely in the browser.
Supported event registrations
The split action interface is available on:
-
game$add_control(); -
game$add_collider(); -
game$add_overlap(); and -
game$add_overlap_end().
For overlaps, mode = "enter" remains the default. Use
mode = "stay" and an appropriate interval only
when an action really must repeat while the objects remain overlapped. A
repeating server_action produces repeating network traffic,
so prefer browser actions for high-frequency visual feedback.
Common mistakes
Passing a regular list
Use the constructor, not list():
# Correct
browser_action = browser_actions(sound$play(), hero$destroy())
# Incorrect
browser_action = list(sound$play(), hero$destroy())A regular list evaluates its elements as R expressions and does not describe a browser action.
Omitting input for a server action
# input is required because this callback runs through Shiny
game$add_control(
"Space",
input = input,
server_action = function(event) do_server_work()
)Using the server for every animation
A server action is valid for arbitrary R logic, but unnecessary round
trips can make controls and animations feel delayed. If an operation is
fully supported by browser_actions() and does not require a
server decision, keep it in the browser.