Giter Club home page Giter Club logo

unifir's Introduction

unifir: A Unifying API for Working with Unity in R

DOI License: MIT Project Status: Active – The project has reached a stable, usable state and is being actively developed. Lifecycle: maturing CRAN status R-CMD-check Codecov test coverage Status at rOpenSci Software Peer Review

unifir is a unifying API for creating and managing Unity scenes directly from R (without requiring GUI interaction). Users are able to write natural-feeling R code to create “scripts” (C# programs) composed of “props” (C# methods) which produce scenes inside the Unity engine. While it is entirely possible to create fleshed-out Unity scenes through this package, unifir is primarily designed around being easy to wrap in other packages, so that any number of packages interacting with Unity can all share a common framework.

The first function most unifir workflows will call is make_script, which creates a “script” object. In that script, we can tell unifir things like where we want to save our project:

library(unifir)
script <- make_script(project = "path/to/project")

We can then iteratively add props to our project, building up a series of commands that we’ll execute in sequence to create a Unity scene:

script <- add_default_player(script, x_position = 10)
script <- add_light(script)
script <- save_scene(script)

All of these functions are designed with the pipe in mind, letting you easily simplify your code:

script <- make_script(project = "path/to/project") |> 
  add_default_player(x_position = 10) |> 
  add_light() |> 
  save_scene()

Once all your props are set, it’s time to execute the script via the action function! This function will create a new Unity project (if necessary), write your “script” object to a C# file, and execute it inside the Unity project.

action(script)

Open your new project from UnityHub, open the scene you created with save_scene, and you’ll see the outputs from your script right in front of you! To learn more, check out the vignettes that ship with the package – particularly the one for how to use unifir to make scenes and the one for how to extend unifir in your own packages.

Right now, unifir wraps the elements of the Unity API that I’ve found useful in my own work, principally focused around creating GameObjects, lights, player controllers, and terrain surfaces. If you are interested in pieces of the API that haven’t made it into unifir yet, please open an issue or a PR!

Installation

The easiest way to install unifir is to install it directly from R-Universe:

# Enable universe(s) by rOpenSci
options(repos = c(
  mikemahoney218 = 'https://ropensci-universe.dev',
  CRAN = 'https://cloud.r-project.org'))

# Install unifir
install.packages('unifir')

You can also install unifir from GitHub with:

# install.packages("remotes")
remotes::install_github("ropensci/unifir")

Note that right now there is no release version of unifir; all published versions are considered “development” versions. While the unifir API is solidifying, it is not yet solid, and breaking changes may happen at any time.

While unifir can be used by itself, you’ll probably want to install Unity in order to actually make Unity projects.

Why do this?

There’s been a lot of interest in using immersive virtual environments for research on topics including science communication, landscape planning, environmental economics and beyond. This is an incredibly exciting area of research, which looks likely to present new methods for participatory planning, data visualization, and skill training. However, right now, much of the research in this area relies upon closed-source tooling which puts up hurdles in front of the equitability, accessibility, and interoperability of the field. In addition, a challenge when assessing hand-build environments (say to assess participant preferences between two scenes) is that us as researchers might accidentally “tilt the scales” in favor of our preferred option, by putting a little more effort into making the scene we personally prefer look more aesthetically pleasing than the alternative.

unifir is a first step towards an open-source, fully reproducible approach for constructing immersive virtual environments. While it currently only targets Unity, a proprietary source-available game engine, unifir hopes to improve the openness and interoperability of immersive virtual environments by encoding all of the decisions involved in building a scene in standard R and C# code. A partially open system is better than a fully closed one, and if other game engines become more feasible options for large scale immersive virtual environments, unifir may be extended to support those as well.

Citing unifir

To cite unifir in publications please use:

Mahoney M. J., Beier C. M., and Ackerman, A. C. (2022). unifir: A Unifying API for Working with Unity in R. Journal of Open Source Software, 7(73), 4388, https://doi.org/10.21105/joss.04388

A BibTeX entry for LaTeX users is:

  @Article{,
    year = {2022},
    publisher = {The Open Journal},
    volume = {7},
    number = {73},
    pages = {4388},
    author = {Michael J. Mahoney and Colin M. Beier and Aidan C. Ackerman},
    title = {{unifir:} A Unifying {API} for Working with {Unity} in {R}},
    journal = {Journal of Open Source Software},
    doi = {10.21105/joss.04388},
    url = {https://doi.org/10.21105/joss.04388},
  }

Code of Conduct

Please note that the unifir project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

Disclaimer

These materials are not sponsored by or affiliated with Unity Technologies or its affiliates. “Unity” is a trademark or registered trademark of Unity Technologies or its affiliates in the U.S. and elsewhere.

ropensci_footer

unifir's People

Contributors

mikemahoney218 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

unifir's Issues

Terrain(r) importer

First step for unifir: automatically import terrain tiles created via terrainr. The ideal UI is still an open question for me -- should the user even know tiles are being made? should there be a function that takes a single elevation raster and makes it in Unity? -- as is how to get there; this issue is to collect scraps I find while looking around for ideas.

Example create_terrain() call fails on action()

I attempted to create a terrain with the example, but it reported there was an exception. Is there a way to see that exception?

library(unifir)

raster <- tempfile(fileext = ".tiff")
r <- terra::rast(matrix(rnorm(1000^2, mean = 100, sd = 20), 1000),
                 extent = terra::ext(0, 1000, 0, 1000)
)
terra::writeRaster(r, raster)


script <- make_script(project = "projects/example_script") |>
  add_light(light_type = "Directional", light_name = "Sun",
            x_rotation = 35, y_rotation = 20) |>
  add_default_player() |>
  create_terrain(
    method_name = "MakeTerrain",
    heightmap_path = raster,
    x_pos = 0,
    z_pos = 0,
    width = 1000,
    height = terra::minmax(r)[[2]],
    length = 1000,
    heightmap_resolution = 1000
  ) |>
  save_scene(scene_name = "my_scene") |>
  set_active_scene(scene_name = "my_scene")

action(script)
# Aborting batchmode due to failure:
# executeMethod method MakeTerrain.MainFunc threw exception.
#> Error in action(script): 1

Issue finding Unity on Windows

I'm having issues finding Unity on Windows:

library(unifir)
unity_path <- file.path("C:","Program Files","Unity","Editor")
file.exists(unity_path)

[1] TRUE

options(unifir_unity_path=unity_path)
Sys.getenv("unifir_unity_path")

[1] "c:/Program files/Unity/Editor"

unity_version()
Error in system(paste(unity, "-version"), intern = TRUE) : 
  'c:/Program' not found

Here is my session info:

R version 4.2.2 (2022-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19044)

Matrix products: default

locale:
[1] LC_COLLATE=Dutch_Netherlands.utf8  LC_CTYPE=Dutch_Netherlands.utf8    LC_MONETARY=Dutch_Netherlands.utf8
[4] LC_NUMERIC=C                       LC_TIME=Dutch_Netherlands.utf8    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] unifir_0.2.2

loaded via a namespace (and not attached):
[1] compiler_4.2.2 R6_2.5.1       tools_4.2.2 

Issue finding Unity on MacOS and Windows

Had issues finding Unity on MacOS. This is what I saw in my interactive session:

library(unifir)

options(unifir_unity_path="/Applications/Unity/Hub/Editor/2021.3.0f1/Unity.app/Contents/MacOS/Unity")
find_unity() # success on first call
# [1] "\"/Applications/Unity/Hub/Editor/2021.3.0f1/Unity.app/Contents/MacOS/Unity\""
find_unity() # failure on second call
# Error in find_unity() : Couldn't find Unity executable at provided path. 
# Please make sure the path provided to 'unity' is correct.
Sys.getenv("unifir_unity_path")
# [1] "\"/Applications/Unity/Hub/Editor/2021.3.0f1/Unity.app/Contents/MacOS/Unity\""

It works if I remove the logic that adds quotation marks to the path and add a beginning slash on this line:

https://github.com/mikemahoney218/unifir/blob/2e5f90cfc09b4456b3751f932f19da49e0dbebc7/R/find_unity.R#L86

On Windows, the windows_location() works fine, but file.exists(unity) always returns false with the extra " added to the path. I think this means even if the user profiles a different unity path, it will always select the default system one.

Relatedly, if I run the test locally on MacOS, I get this failure:

Failure (test-make_script.R:64:3): make_script always produces the same script
example_script$unity (`actual`) not identical to saved_script$unity (`expected`).

`actual`:   "junk_string"    
`expected`: "\"junk_string\""

Release unifir 0.2.4

Prepare for release:

  • git pull
  • Check current CRAN check results
  • Polish NEWS
  • urlchecker::url_check()
  • devtools::build_readme()
  • devtools::check(remote = TRUE, manual = TRUE)
  • devtools::check_win_devel()
  • revdepcheck::revdep_check(num_workers = 4)
  • Update cran-comments.md
  • git push

Submit to CRAN:

  • usethis::use_version('patch')
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted 🎉
  • usethis::use_github_release()
  • usethis::use_dev_version(push = TRUE)

associated_coordinates() example errors due to missing CRS

Here's the reprex I ran:

library(unifir)
simulated_data <- data.frame(
  id = seq(1, 100, 1),
  lat = runif(100, 44.04905, 44.17609),
  lng = runif(100, -74.01188, -73.83493)
)
simulated_data <- sf::st_as_sf(simulated_data, coords = c("lng", "lat"))
output_files <- terrainr::get_tiles(simulated_data)
#> Warning in get_tiles.sf(simulated_data): Assuming geographic CRS. Set
#> 'projected' to TRUE if projected.
#> Warning in handle_bboxSR(data, projected): Assuming CRS of EPSG 4326 (set bboxSR
#> explicity to override)
temptiff <- tempfile(fileext = ".tif")
terrainr::merge_rasters(output_files["elevation"][[1]], temptiff)
associate_coordinates(simulated_data, temptiff)
#> Error in st_transform.sfc(st_geometry(x), crs, ...): cannot transform sfc object with missing crs

Add full scene example to user vignette

The current user guide describes the individual functions, but doesn't provide a complete example of a script. A few related thoughts on how a complete example could help:

  • It took a minute to read the fine print about needing to set_active_scene(). Helpful to show that as part of the example script so it's obvious I need to include that.
  • The coordinate system with x and z as horizontal and y as vertical wasn't obvious at first. Might be worth describing that in user guide.
  • There's also a question of unit size; by default should units be considered meters? If other packages are providing assets, it might be helpful if they are scaled in some standard way.
  • add_default_tree() seems like it can take vectors for position and other arguments (great!), though I didn't find that obvious from docs since most example show a scalar provided there.

Here is an example scene I put together while testing the package:

library(unifir)
library(terra)

num_trees <- 100
pos <- data.frame(
  x = runif(num_trees, -40, 40),
  z = runif(num_trees, -40, 40)
)

plot(pos$x, pos$z)

project_path <- file.path("projects", "random-trees")
# heightmap_path <- normalizePath(file.path(project_path, "Assets", "heightmap.tiff"))
# terra::writeRaster(heightmap, heightmap_path, overwrite = TRUE)

tree_script <- make_script(project = project_path) |>
  add_light(light_type = "Directional", light_name = "Sun",
            x_rotation = 35, y_rotation = 20) |>
  add_default_player()
# Does seem to have a user-facing way to create a ground, so player will always fall


raster <- tempfile(fileext = ".tiff")
r <- terra::rast(matrix(rnorm(1000^2, mean = 100, sd = 20), 1000),
                 extent = terra::ext(0, 1000, 0, 1000)
)
terra::writeRaster(r, raster)
tree_script <- create_terrain(
  tree_script,
  heightmap_path = raster,
  x_pos = -125,
  z_pos = -125,
  width = 1000,
  height = 0.1,
  length = 1000,
  heightmap_resolution = 1000
)

tree_script <- add_default_tree(
  tree_script,
  "tree_1",
  x_position = pos$x,
  z_position = pos$z,
  x_rotation = -90 # should we make this default?
)

tree_script <- tree_script |>
  save_scene(scene_name = "trees") |>
  set_active_scene(scene_name = "trees")

action(tree_script)

Release unifir 0.2.3

Prepare for release:

  • git pull
  • Check current CRAN check results
  • Polish NEWS
  • devtools::build_readme()
  • urlchecker::url_check()
  • devtools::check(remote = TRUE, manual = TRUE)
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • revdepcheck::revdep_check(num_workers = 4)
  • Update cran-comments.md
  • git push

Submit to CRAN:

  • usethis::use_version('patch')
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted 🎉
  • git push
  • usethis::use_github_release()
  • usethis::use_dev_version()
  • git push

Release unifir 0.2.2

Prepare for release:

  • git pull
  • Check current CRAN check results
  • Polish NEWS
  • devtools::build_readme()
  • urlchecker::url_check()
  • devtools::check(remote = TRUE, manual = TRUE)
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • revdepcheck::revdep_check(num_workers = 4)
  • Update cran-comments.md
  • git push

Submit to CRAN:

  • usethis::use_version('patch')
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted 🎉
  • git push
  • usethis::use_github_release()
  • usethis::use_dev_version()
  • git push

Release unifir 0.2.2

Prepare for release:

  • git pull
  • Check current CRAN check results
  • Polish NEWS
  • devtools::build_readme()
  • urlchecker::url_check()
  • devtools::check(remote = TRUE, manual = TRUE)
  • devtools::check_win_devel()
  • rhub::check_for_cran()
  • revdepcheck::revdep_check(num_workers = 4)
  • Update cran-comments.md
  • git push

Submit to CRAN:

  • usethis::use_version('patch')
  • devtools::submit_cran()
  • Approve email

Wait for CRAN...

  • Accepted 🎉
  • git push
  • usethis::use_github_release()
  • usethis::use_dev_version()
  • git push

General directions

Where I sit right now, unifir needs 4 key features in order to be useful; potentially in order to be useful enough to put on CRAN:

  1. Terrain import -- provide any raster object (or object readable via raster::raster) and convert it into terrain objects
  2. Arbitrary object placement -- ideally we could take things like STL, FBX, blend files and use those as objects to import into a given x/y/z coordinate (relative to terrain positions?), but if this has to look like "import unity prefab" then it has to look like "unity prefab"
  3. Add a first-person controller to a scene. Need to check the licensing of the abandoned standard asset pack to see how much of that code I can extract.
  4. A way to execute these in sequence, without needing to open Unity multiple times -- the majority of time involved in this process is the loading of the Unity exe and scene, so ideally we'd be adding methods to a C# file and then calling an init function at the end. If this can be combined with create_project that'd be awesome.

I'm sure this scope will creep dramatically as time goes on, particularly as I get further into my tree work. But I think this is the steering I should focus on for 0.1.0 (and to add unifir as a dependency for terrainr)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.