Code Monkey home page Code Monkey logo

scala-games's Introduction

DISCLAIMER

This project and its design is in early stages. I am currently working on finishing a stable rendering pipeline, which should be the first major component of the engine that will be complete.

scala-games

A minimalistic game engine for rapid prototyping using Scala 3.

Why Scala?

In short, power and clean code.

Here are some further points that cover the viability of using Scala to develop games:

  • Write a solution once, use it everywhere (no duplicate patterns): Scala allows you to create abstract solutions for the common patterns you need to solve in your projects. Which also makes it an outstanding choice for community made libraries.
  • Concise and powerful code: Where you boilerplate in other languages, you usually have a tidy solution in Scala, e.g. Setters And Getters.

Checkout the Scala Pros And Cons wiki page for some examples of why Scala is a great language for game developers.

If you have any questions or suggestions on using Scala for the purpose of writing the engine and developing games, please check out the Scala category in Discussions.

Project Goals

  • Easy to learn for newcomers with a concise API.
  • Powerful features for prototyping game mechanics quickly.
  • Extensible and modular, so that developers may customize the engine to their liking by easily rewriting its components for their use cases or adding libraries to add content and case-specific features. (Abstractions where possible improve flexibility)
  • Become smaller and more refined over time, rather than larger and more monolithic. Extreneous features should be made available through community-made libraries. (This is great for maintainability and keeping a small learning curve)
  • Stable, flexible API.
  • Include generic libraries to reduce project startup time in the 'lib' package, e.g. a highly configurable levelling system.
  • Prioritize easy of use, over performance.

To Avoid

  • Forcing a certain type of development style. Developers should be the ones to choose what they focus on and how they like to structure their projects. E.g. if you want to create a combat system, you shouldn't have to think about how things are rendered.
  • Unnecesary features, e.g. 3 different types of buttons. These types of features should be made available through seperate libraries by the community, so that the main project can stay focused on its goals and remain maintainable by hopefully becoming smaller, rather than larger over time. The generic libraries mentioned in the Porject Goals are considered to be neccesary tools for rapid prototyping of game mechanics.

Roadmap

  • Stable render-pipeline with basic features:
    • Basic classes for rendering: polygons, lines, sprites. (With batch-rendering for polygons and lines)
    • Compatibility with user defined RenderManagers and RenderedElements for extensibility. (Basically making it possible for users to recreate the rendering pipeline or simply add custom particle systems, etc.)
    • Simple post-processing with bloom, anti-aliasing and the option for user defined shaders to be set instead of the built-in shaders. (The engine should come with multiple post-processing shaders to choose from, with one set by default ofcourse. E.g. one with just bloom, or one with anti-aliasing and bloom).
  • Physics with:
    • Rigidbodies.
    • Non-rigidbodies for detection only.
  • Compile time generated Node classes, which are memory safe and destroyable by the engine.

scala-games's People

Contributors

accmltr avatar

Stargazers

spamegg avatar Jack avatar Otizhi avatar  avatar

Watchers

 avatar

scala-games's Issues

Add ImageSection, QuadrantSprite & NinePatchSprite

ImageSection defines a section of an Image to be seen used where an Image is expected.

Quadrant sprite takes an image that will be mirrored in all quadrants of the Cartesian Coordinate system. A corner size is given, and the edges and center will automatically be repeated to fill any rectangular size set for the object - similar to NinePatchRect in Godot.

Finally add NinePatchSprite, the same as in Godot.

Anchors

Add conceptual anchor points to all classes which exist in the 2D space, and have them be extendable in subclasses.

You should be able to set and get anchor points, such that the following is possible:

val rect = RectSdf(
  width = 100
  height = 30
)
val text = Text()

// The MyCustomPlayer class is an imaginary user defined class which sets its 
// gun position based on player aim and player character behavior. This is added
// as an anchor point to the class.
val player = MyCustomPlayer(
  health = 10
  radius = 22.3
)

def loop(): Unit = 
  // player has already been updated at this point...
  
  // Have the rect's bottom left corner be moved onto the gun position with an offset added to it
  rect.anchor.bottomLeft = player.anchor.gunPos + Vector2(10,10)
  
  // Display the ammo count and move the text
  text.content = s"${player.ammo}/${player.maxAmmo}"
  text.anchor.center = rect.anchor.center

Some global anchors to consider:

  • bottomRight
  • center
  • bottomLeft
  • topLeft
  • topRight

Make RenderData mutable

Currently, with an immutable RenderData class, its unnecessarily difficult for users to work with it.

Since the intended purpose for these types of classes are for users to change their properties over time, making them immutable adds unnecessary overhead to the development process.

They will then be processed with the risks of their mutable states considered.

Fix V-Sync Setting

Currently setting for a Game before the init call it doesn't do anything.

Enable batch-rendering

Overview

If there are RenderManager and RenderedElement classes, the render pipeline can use the managers to render the elements that belong to them. This can be used to do batch rendering, e.g. a PolygonRenderManager could group PolygonRenderedElement instances by layer and then render groups by writing all vertices and indices for the polygons into single buffers and render them in one draw-call.

The RenderManager and RenderedElement should also be extendable by user classes, so users have the potential to render anything the way they want to and optimize it for their specific use cases.

Engine Performance

Batch rendering should then be implemented for all out-of-the-box elements that are included in the engine: polygons, lines, textures etc.

References

The following old commit contains an rough implementation of this system:

24bd3c2

Hide unnecessary API

Hide all classes and methods that aren't needed by users by making them as private as possible.

Test RenderedData

Thoroughly test constructors.

Note: Make sure to check for duplicate entries to 'uniforms' and make sure uniforms are checked when passed to sub-classes, and check in super is not skipped.

Create LineRenderedElement, ColoredLineRenderedElement

Overview of LineRenderedElement

Class that contains all the data needed to render a 2D line with some nice features.

By default the engine should render this line class as a sequence of rectangles with the flat color provided by the 'tint' shader uniform.

Optional features:

  • 'segment' RenderedElement for line segment (renders rects by default with tint color)
  • 'corner' RenderedElement to render on corners (defaults to rendering flat circles with same 'tint' and 'width' as the line). Can be replaced by anything, could render a polygon or custom element. Passes a 'angle
  • 'end', 'start' RenderedElement (use same RenderedElement for both if desired, or use distinct ones)

Overview of ColoredLineRenderedElement

Create 'ColoredLineRenderedElement' which extends 'LineRenderedElement' but hides its features and has the following properties:

  • 'endStyle' - none, sharp, round (enum) | Ngon
  • 'startStyle' - none, sharp, round (enum) | Ngon
  • 'cornerStyle' - none, sharp, round (enum) | Ngon

Fix polygon render bug

The following leaves an unfilled triangle:

val p1 = RenderedMesh(
      shader = shader,
      transform = Matrix3
        .transform(
          translation = Vector2(
            cos(1.7f * Time.current + 5f) * .2,
            sin(3f * Time.current + 3f) * .11
          ),
          rotation = cos(.8f * Time.current + 3f) * 2f * pi,
          scale = Vector2(1f, 1f) * (.2f + .1f * (cos(.8f * Time.current) + 1))
        ),
      mesh = Mesh(
        Polygon(
          Vector(
            Vector2(0, 0),
            Vector2(1, 0),
            Vector2(1, 1),
            Vector2(0, 1),
            Vector2(0, .8),
            Vector2(0.35, 0.5),
            Vector2(0, .2)
          ).map(v => Vector2(v.x - .5, v.y - .5))
        )
      ),
      tint = Color.YELLOW * .75
    )

I assume this happens since the first and last line segments found distinct valid points, and their triangles don't share an edge.

Screenshots:
image
image

Flatten RenderData constructors and replace with sub-classes

Currently, to render a polygon to the screen, you pass a Polygon instance to one of the RenderData constructor overloads and it processes that data into vertices and indices which will be passed to OpenGl. But since we intend to make RenderData mutable in #29, splitting the single RenderData class into sub-classes and making those classes non-reliable on classes from the math package` will make the most sense.

Add Image

Make an Image class that will handle loading images of different file types via STB Loader.

Some images render incorrectly

soldier-paladin-digital-art-gun-wallpaper-907ffdf3e20334b701b15a7cc7668b54

The Sprite class can not render this image correctly:
Screenshot from 2024-02-10 13-55-00

Note: The only problems are that the image is skewed and rendered without color, the upside down render was a different problem which was fixed.

Maybe it is since it is a JPEG, but this needs further investigation.

Remove bloated constructor parameters

Only have the bare necessary parameters in constructors, have other properties be edited after construction!

E.g.

val b = Button(text="MyButton", style=....

Rather:

val b = Button("MyButton")
b.style=...

Note: This is just a rough idea for now. Still to be considered.

Polygon Edges Too Pointy After Grown

Change the Polygon.grow method to have a rounding_interval parameter. This parameter will specify the maximum distance on an equidistance arche on a corner of a polygon when inflated. Since, when inflating polygons, the points end up being further from their original positions as the edges of the polygon. New edges should be made on what would be a perfectly rounded mathematical corner for a polygon inflated exactly.

Note: The perimeter length of the arche formed on a corner should be pre-calculated, then only should points be placed in equal spacing of one another, while being under the grow amount.

Ensure games close without any traces

Quote from Copilot:

When closing a game or any application, it's important to perform cleanup operations to ensure that all resources are properly released and to prevent memory leaks. Here are some typical cleanup tasks you might need to perform:

Freeing Graphics Resources: This includes deleting any textures, buffers, and shaders you've created. In the context of your code, you might need to delete the shader and any meshes you've created.

Stopping any Running Threads: If your game has any background threads running, you should ensure they are stopped properly.

Saving Game State: If your game has any state that needs to be persisted (like player progress or high scores), you should save this to disk.

Releasing Windowing Resources: If you're using a library like GLFW to create your game window, you should call the appropriate function to destroy the window and release any associated resources.

Closing any Open Files: If your game has any files open, you should close them.

Releasing any Network Resources: If your game uses any network resources (like sockets), you should ensure they are properly closed.

Remember, the specific cleanup operations you need to perform will depend on what resources your game uses.

Create ShaderCompileManager

Overview

This class would track all existing shaders and serve as a shader factory, providing references to existing shaders instead of creating duplicates. Causing shaders to only be compiled and created once and be used by all instances needing them.

'ShaderCompileManager' must have a 'keepAlive' list of shaders which the user can add and remove from at will. Adding to this list should return an async result which can be waited for. This can then, for example, be used to pre-compile shaders for scenes in their loading screens.

Add Framecatch Renderer

This renderer simply adds a shader in at various points in the render pipeline which output the current frame to a frame buffer object, and then into an image local variable.

These images can be read every frame for the purposes of testing.

Create SDF render sub-package

Add some SDF rendered elements for primitives in new package render.render_element.sdf. This would be great for making the UI package look nice down the line.

SDFs to include:

  • Circle
  • Square
  • Capsule

Simplify `render` package

Overview

Remove the system for batch rendering and render all RenderedElements individually without concern for performance.

Reasoning

This over complicates the project while its still in early stages and is undergoing constant change.

It will make it easier to test and clean.

Add Cursor Customization

Add the following to render.window.Window:

  • cursor setter and getter
  • clearCursor()
  • A default cursor for the engine.

Make 'RenderMaster' API stateless

Remove the need to add and remove 'RenderedElement' in order to track and render them. Instead, 'RenderMaster' should be passed all the elements it should rendered every 'render' call.

However, internally, 'RenderMaster', 'RenderManager' and 'RenderedElement' may still store data about previous render calls for optimization.

Classes that use local state for optimization should explain this in their docs, so that users may know which of these classes are best to keep around for reuse, and which can be terminated after a single 'render' call.

Ensure built-in uniforms are uploaded

Make sure all Renderer inheritors upload default uniforms.

Maybe add a test-mode listener inside Shader.uploadUniforms and all other uniform upload methods and test Renderers.

OR

Find a new way to streamline uploading uniforms so that all Renderers cannot avoid uploading defaults.

Fix Color Addition

Code:

//... some other code ...
var sprite: Sprite = Sprite(
    "res/sample_image.png"
  )
  sprite.color = Color.GREEN
  sprite.position = Vector2(50, 50)
  sprite.width = 100
  sprite.height = 100
//... some other code ...
onUpdate += { (delta: Float) =>

    //... some other code ...

    if (input.justPressed(KeyCode.s)) {
      sprite.width += 10
      sprite.height += 10
      sprite.rotation += 0.1f
      sprite.color += Color(0.1f, 0.1f, 0.1f)
    }
    if (input.justPressed(KeyCode.d)) {
      sprite.width -= 10
      sprite.height -= 10
      sprite.rotation -= 0.1f
      sprite.color -= Color(0.1f, 0.1f, 0.1f)
    }

    if (input.justPressed(KeyCode.v)) {
      window.vsync = !window.vsync
    }

    if (input.justReleased(KeyCode.escape)) {
      quit()
    }
  }

Error when pressing 'S':

Node(name: MyFirstNode, position: (0.000, 0.000), children: 4, components: 0)
Running Scala Games on LWJGL 3.3.3+5!
Exception in thread "main" java.lang.ExceptionInInitializerError
        at MyGame.main(Main.scala)
Caused by: java.lang.IllegalArgumentException: 'g' out of bounds: [0,1]
        at engine.render.Color.<init>(Color.scala:16)
        at engine.render.Color$.apply(Color.scala:6)
        at engine.render.Color.$plus(Color.scala:52)
        at MyGame$.$init$$$anonfun$2(Main.scala:100)
        at scala.runtime.java8.JFunction1$mcVF$sp.apply(JFunction1$mcVF$sp.scala:18)
        at engine.scene.Signal.emit$$anonfun$1(Signal.scala:13)
        at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
        at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
        at scala.collection.immutable.List.foreach(List.scala:333)
        at engine.scene.Signal.emit(Signal.scala:13)
        at engine.Game.updateCallback(Game.scala:79)
        at engine.Game.$init$$$anonfun$2(Game.scala:41)
        at engine.render.window.Window._runLoop(Window.scala:236)
        at engine.render.window.Window.run(Window.scala:85)
        at engine.Game.run(Game.scala:53)
        at MyGame$.<clinit>(Main.scala:118)
        ... 1 more

No error when pressing 'D', but sprite completely disappears.

Window Strech Modes

You should be able to set width or height (in pixels) that you want to render to, and have it fill the screen on your window, even when the window doesn't match the output resolution. Or you can have no streching, then the window resolution is always the output resolution.

Add `engine.math` overloads that work with doubles

Make this work:

val f: Float = 0.23413229812341
val d: Double = 0.23413229812341
println(s"float 'math.abs' result: ${engine.math.abs(f)}")
println(s"double 'math.abs' result: ${engine.math.abs(d)}")

Then users and engine devs can seamlessly switch to higher precision for operations that require it.

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.