lopix

package module
v0.0.0-...-7ea1aa6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 20, 2024 License: MIT Imports: 6 Imported by: 2

README

lopix

Go Reference

A package to assist the development of very simple Ebitengine pixel art games.

This package allows you to implement your game using a logical resolution and ignoring Game.Layout() completely. Scaling is managed internally by lopix with pixel art aware scaling algorithms.

Context

There are multiple models for pixel art games:

  • The simplest model consists in aligning both the graphical elements and the game view to a logical pixel grid. In most cases, these games don't have a notion of "camera" and use fixed screens. This is what lopix implements.
  • The next model aligns graphical elements to a logical pixel grid, but gives the camera the freedom to move smoothly through non-integer coordinates. Pixel art RPGs would be a common genre for this. This is what mipix implements.
  • The final and more modern approach revolves around floating point positioning for both graphical elements and the camera. Different strategies are possible when it comes to projecting from the "logical space" to the final high resolution canvas. Most pixel art games use this approach nowadays... at least when they aren't doing outright 3D already.

My original intent was to create a library that would help with all the pixel-art game models... but it soon became obvious that the workflows are different enough to warrant different packages for each one.

All in all, it must be explicitly said that lopix implements a very limited and restrictive model; the package might have more educative value than practical value. Don't be afraid to look into mipix or roll your own solutions if you need to.

Code example

https://github.com/tinne26/lopix-examples

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AutoResizeWindow

func AutoResizeWindow()

Sets the windowed size to a reasonably visible but still pixel-perfect value.

If the game is currently fullscreened, it will remain fullscreened, but the windowed size will still be updated.

func DrawPixel

func DrawPixel(target *ebiten.Image, x, y int, rgba color.RGBA)

Helper function to draw a single pixel.

func DrawRect

func DrawRect(target *ebiten.Image, rect image.Rectangle, rgba color.RGBA)

Helper function to draw filled rectangles. Unlike ebiten.Image.Fill(), it doesn't replace the contents of the rect area, it draws over it.

func GetLogicalBounds

func GetLogicalBounds() image.Rectangle

Utility function, equivalent to obtaining the resolution and returning a rectangle of that size with (0, 0) origin.

func GetResolution

func GetResolution() (width, height int)

Returns the last pair of values passed to SetResolution().

With lopix, the game resolution and the size of the canvas passed to Game.Draw() are always the same.

func HiResActiveArea

func HiResActiveArea() image.Rectangle

While drawing in high resolution, sometimes not the whole canvas is used due to aspect ratio mismatches. This function returns the active area for the high resolution canvas.

Notice that QueueHiResDraw() callbacks receive the full resolution canvas in case you want to fill the black margins yourself.

func QueueDraw

func QueueDraw(callback func(*ebiten.Image))

See QueueHiResDraw(). If you need to interleave high resolution and logically rendered layers, you might need to make use of use this function. If you aren't using QueueHiResDraw() in the first place, you should ignore this function.

Notice that the canvas passed to the callback will be preemptively cleared if the previous draw was a high resolution draw.

Must only be called from Game.Draw() or successive draw callbacks.

func QueueHiResDraw

func QueueHiResDraw(callback func(*ebiten.Image))

Queues the given callback function to be invoked after the current draw function and any other queued draws finish.

Despite lopix focusing on low resolution pixel art games, in some cases you might still want to render vectorial UI, apply shaders for screen effects, draw high resolution backgrounds... and you can't do that on the logical canvas.

This method can only be invoked during the draw stage. Multiple draws might be queued. See also QueueDraw().

Must only be called from Game.Draw() or successive draw callbacks.

func Run

func Run(game Game) error

Equivalent to ebiten.RunGame(), but expecting a lopix Game instead of an ebiten.Game.

You must remember to SetResolution() before this.

func SetResolution

func SetResolution(width, height int)

Must be called at least once before Run().

Multiple resolutions are typically only relevant if you are trying to support different aspect ratios (e.g. ultrawide), which isn't even suitable for all types of games... and definitely not a common concern for the kind of games lopix tries to support.

See also GetResolution().

Must only be called during initialization or Game.Update().

func SetScalingFilter

func SetScalingFilter(filter ScalingFilter)

Changes the scaling filter. The default is Hermite.

Must only be called during initialization or Game.Update().

The first time you set a filter explicitly, its shader is also compiled. This means that this function can be effectively used to precompile the relevant shaders. Otherwise, the shader will be recompiled the first time it's actually needed in order to draw.

func SetScalingMode

func SetScalingMode(mode ScalingMode)

Changes the scaling mode. The default is Proportional.

Must only be called during initialization or Game.Update().

func ToLogicalCoords

func ToLogicalCoords(x, y int) (int, int)

Transforms coordinates obtained from ebiten.CursorPosition() and similar functions to coordinates within the game's logical resolution.

func ToRelativeCoords

func ToRelativeCoords(x, y int) (float64, float64)

Transforms coordinates obtained from ebiten.CursorPosition() and similar functions to relative coordinates between 0 and 1.

If the coordinates fall outside the active canvas they will be clamped to the closest point inside it, returning 0 or 1 for any clamped axis.

Types

type Game

type Game interface {
	// Updates the game logic
	Update() error

	// Draws the game contents
	Draw(*ebiten.Image)
}

Similar to ebiten.Game, but without the Layout() method: we only need a logical resolution, which is set at Run().

type RedrawManager

type RedrawManager controller

In some games and applications it's possible to spare GPU by using ebiten.SetScreenClearedEveryFrame(false) and omitting redundant draw calls.

The redraw manager allows you to synchronize this process with lopix itself, as there are some projections that would otherwise fall outside your control.

By default, redraws are executed on every frame. If you want to manage them more efficiently, you can do the following:

func Redraw

func Redraw() *RedrawManager

See RedrawManager.

func (*RedrawManager) IsManaged

func (self *RedrawManager) IsManaged() bool

Returns whether manual redraw management is enabled or not.

func (*RedrawManager) Pending

func (self *RedrawManager) Pending() bool

Returns whether a redraw is still pending. Notice that besides explicit requests, a redraw can also be pending due to a canvas resize, the modification of the scaling properties, etc.

func (*RedrawManager) Request

func (self *RedrawManager) Request()

Notifies lopix that the next Game.Draw() needs to be projected on the screen. Requests are typically issued when relevant input or events are detected during Game.Update().

This function can be called multiple times within a single update without issue, it's only setting an internal flag equivalent to "needs redraw".

func (*RedrawManager) ScheduleClear

func (self *RedrawManager) ScheduleClear()

Signal the redraw manager to clear both the logical screen and the high resolution canvas before the next Game.Draw().

func (*RedrawManager) SetManaged

func (self *RedrawManager) SetManaged(managed bool)

Enables or disables manual redraw management. By default, redraw management is disabled and the screen is redrawn every frame.

Must only be called during initialization or Game.Update().

type ScalingFilter

type ScalingFilter uint8

Scaling filters can be changed through SetScalingFilter().

Many filters are only provided as comparison points, not because they necessarily offer great results. For the purposes of this package, I'd generally recommend the AASampling* or the Hermite filters.

In some very specific cases, Nearest might also be useful.

const (
	Hermite ScalingFilter = iota
	AASamplingSoft
	AASamplingSharp
	Nearest
	Bicubic
	Bilinear
	SrcHermite
	SrcBicubic
	SrcBilinear
)

func GetScalingFilter

func GetScalingFilter() ScalingFilter

Returns the current scaling filter. The default is Hermite.

func (ScalingFilter) String

func (self ScalingFilter) String() string

Returns a string representation of the scaling filter.

type ScalingMode

type ScalingMode uint8

Scaling modes can be changed through SetScalingMode().

Letting the player change these through the game options is generally nice. The default mode is Proportional.

const (
	// Proportional projects the screen to be displayed as big as
	// possible while preserving the game's aspect ratio.
	Proportional ScalingMode = iota

	// Also known as "integer scaling". Depending on the screen or
	// window size, a lot of space could be left unused... but at
	// least the results tend to be as sharp as possible.
	PixelPerfect

	// Completley fill the screen no matter how ugly it might get.
	Stretched
)

func GetScalingMode

func GetScalingMode() ScalingMode

Returns the current scaling mode. The default is Proportional.

func (ScalingMode) String

func (self ScalingMode) String() string

Returns "Propotional", "Pixel-Perfect" or "Stretched".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL