Closed ww9 closed 1 year ago
Sure thing, below is a minimal example without having to bootstrap a full VISU application.
Still, the amount of code might still seem a bit ridiculous considering the target is just getting an image rendered on screen.
The thing is, the complexity gets high quickly when creating a game (as you for sure know already). And in many cases you need a lot of control over the things that make it work. VISU does not yet provide a lot of "boilerplate templates" so for a base setup there is quite a bit of things needed.
The example below should cover the basics:
I hope it will help 🙂
For the example create a new directory then install the required dependencies:
composer init -s dev -n
composer require phpgl/visu
composer require --dev phpgl/ide-stubs
And the example:
example.php
<?php
use ClanCats\Container\Container;
use GL\Math\{Vec3, Vec4};
use VISU\Geo\Transform;
use VISU\Graphics\{Camera, CameraProjectionMode};
use VISU\Graphics\GLState;
use VISU\Graphics\QuadVertexArray;
use VISU\Graphics\Rendering\Pass\{BackbufferData, CallbackPass, ClearPass};
use VISU\Graphics\Rendering\{PipelineContainer, PipelineResources};
use VISU\Graphics\Rendering\{RenderPass, RenderPipeline};
use VISU\Graphics\{ShaderProgram, ShaderStage};
use VISU\Graphics\Texture;
use VISU\OS\{Input, Window, WindowHints};
use VISU\Runtime\{GameLoop, GameLoopDelegate};
use VISU\Signal\Dispatcher;
if (!defined('DS')) { define('DS', DIRECTORY_SEPARATOR); }
set_time_limit(0);
require __DIR__ . '/vendor/autoload.php';
/**
* Simple Game class
*/
class Game implements GameLoopDelegate
{
/**
* The texture we want to draw later
*/
private Texture $myImageTexture;
/**
* We need to create at least one shader program to draw the texture
*/
private ShaderProgram $textureDrawShader;
/**
* For a 2D scene like this we need an orthographic camera
*/
private Camera $camera;
/**
* Construct a new game instance
*/
public function __construct(
private Container $container
) {
$window = $this->container->getTyped(Window::class, 'window');
$gl = $this->container->getTyped(GLState::class, 'gl');
// initialize the window
$window->initailize($gl);
// enable vsync by default
$window->setSwapInterval(1);
// make the input the windows event handler
$window->setEventHandler($this->container->get('input'));
// Create the texture object we want to draw later
$this->myImageTexture = new Texture($gl, 'test');
$this->myImageTexture->loadFromFile(__DIR__ . '/vendor/phpgl/visu/resources/phplogo.png');
// Create a 2D camera
$this->camera = new Camera(CameraProjectionMode::orthographic);
$this->camera->nearPlane = -10;
$this->camera->farPlane = 10;
// create a shader to draw the texture
// this shader will take in a model matrix representing the position and scale of the image
$this->textureDrawShader = new ShaderProgram($gl);
$this->textureDrawShader->attach(new ShaderStage(ShaderStage::VERTEX, <<< 'GLSL'
#version 330 core
layout (location = 0) in vec3 a_pos;
layout (location = 1) in vec2 a_uv;
out vec2 v_uv;
uniform mat4 u_view;
uniform mat4 u_projection;
uniform mat4 u_model;
// In this example scale represents size in pixels
// so we need to halve the model scale
mat4 halfscale = mat4(
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.0, 0.0, 0.0, 1.0
);
void main() {
v_uv = a_uv; // pass the texture coordinates to the fragment shader
gl_Position = u_projection * u_view * u_model * halfscale * vec4(a_pos, 1.0);
}
GLSL));
// also attach a simple fragment shader
$this->textureDrawShader->attach(new ShaderStage(ShaderStage::FRAGMENT, <<< 'GLSL'
#version 330 core
in vec2 v_uv;
out vec4 fragment_color;
uniform sampler2D u_texture;
void main() {
vec2 uv = vec2(v_uv.x, 1.0 - v_uv.y);
fragment_color = texture(u_texture, uv);
}
GLSL));
$this->textureDrawShader->link();
}
/**
* Start the game
* This will begin the game loop
*/
public function start() : void
{
// start the game loop
$this->container->getTyped(GameLoop::class, 'loop')->start();
}
/**
* Update the games state
* This method might be called multiple times per frame, or not at all if
* the frame rate is very high.
*
* The update method should step the game forward in time, this is the place
* where you would update the position of your game objects, check for collisions
* and so on.
*
* @return void
*/
public function update() : void
{
$window = $this->container->getTyped(Window::class, 'window');
// poll for new events
$window->pollEvents();
}
/**
* Render the current game state
* This method is called once per frame.
*
* The render method should draw the current game state to the screen. You recieve
* a delta time value which you can use to interpolate between the current and the
* previous frame. This is useful for animations and other things that should be
* smooth with variable frame rates.
*
* @param float $deltaTime
* @return void
*/
public function render(float $deltaTime) : void
{
$window = $this->container->getTyped(Window::class, 'window');
// fetch the render target
$windowRenderTarget = $window->getRenderTarget();
$windowRenderTarget->framebuffer()->clearColor = new Vec4(1, 0.2, 0.2, 1.0);
// retrieve projection and view matrices from the camera
$viewMatrix = $this->camera->getViewMatrix($deltaTime);
$projectionMatrix = $this->camera->getProjectionMatrix($windowRenderTarget);
// construct a rendering pipeline
$data = new PipelineContainer;
$pipeline = new RenderPipeline($this->container->get('pipeline_res'), $data, $windowRenderTarget);
// clear the backbuffer
$pipeline->addPass(new ClearPass($data->get(BackbufferData::class)->target));
// create a render pass for our image
$pipeline->addPass(new CallbackPass(
'ExampleImagePass',
// setup (we need to declare who is reading and writing what)
function(RenderPass $pass, RenderPipeline $pipeline, PipelineContainer $data) {
$pipeline->writes($pass, $data->get(BackbufferData::class)->target);
},
// execute
function(PipelineContainer $data, PipelineResources $resources) use($viewMatrix, $projectionMatrix)
{
// enable our shader and set the uniforms camera uniforms
$this->textureDrawShader->use();
$this->textureDrawShader->setUniformMat4('u_view', false, $viewMatrix);
$this->textureDrawShader->setUniformMat4('u_projection', false, $projectionMatrix);
$this->textureDrawShader->setUniform1i('u_texture', 0);
// bind the texture
$this->myImageTexture->bind(GL_TEXTURE0);
/** @var QuadVertexArray */
$quadVA = $resources->cacheStaticResource('quadva', function(GLState $gl) {
return new QuadVertexArray($gl);
});
// draw the image 5 times
for ($i=0; $i<5; $i++) {
$transform = new Transform;
$transform->position = new Vec3(150 + ($i * 200), 350 + sin(glfwGetTime() + $i * 2) * 150, 0);
$transform->scale = new Vec3(150, 150, 1);
// set the model matrix
$this->textureDrawShader->setUniformMat4('u_model', false, $transform->getLocalMatrix());
// draw the quad
$quadVA->draw();
}
}
));
// run the pipeline
$pipeline->execute(0);
// swap the backbuffer
$window->swapBuffers();
}
/**
* Loop should stop
* This method is called once per frame and should return true if the game loop
* should stop. This is useful if you want to quit the game after a certain amount
* of time or if the player has lost all his lives etc..
*
* @return bool
*/
public function shouldStop() : bool
{
$window = $this->container->getTyped(Window::class, 'window');
return $window->shouldClose();
}
}
/**
* Create a container for the required dependecies
*/
$container = new Container();
$container->set('gl', new GLState);
$container->bindClass('window', Window::class, ['VISU Engine', 1280, 720, new WindowHints]);
$container->bindClass('game', Game::class, ['@container']);
$container->bindClass('loop', GameLoop::class, ['@game']);
$container->bindClass('dispatcher', Dispatcher::class);
$container->bindClass('input', Input::class, ['@window', '@dispatcher']);
$container->bindClass('pipeline_res', PipelineResources::class, ['@gl']);
/**
* Initalize GLFW and begin
*/
glfwInit();
// load & start the game
$game = $container->get('game');
$game->start();
// clean up glfw
glfwTerminate();
This is great! Thank you so much for taking the time to write it, document and share. Very kind of you!
With OOP I believe I can encapsulate most low level functionality.
Again, thanks for the quick and comprehensive reply!
I'll get my hands wet during weekend if possible with some simple 2D game. Perhaps a Pong or Breakout game.
This is totally my fault but just for documentation sake, if it helps someone.
First time I ran php.exe example.php
I got the following error:
PHP Fatal error: Uncaught Error: Undefined constant "VISU\OS\GLFW_OPENGL_CORE_PROFILE" in E:\dev\playground\phpgame\example.php:243
Stack trace:
#0 {main}
thrown in E:\dev\playground\phpgame\example.php on line 243
Then I remembered that I forgot to download the DLL and add extension=glfw
to php.ini. After doing that it works! I just needed to RTFM 🤣
Hi! Is there a simple 2D game example using Visu?
Or perhaps an example of how to load a png image and render on screen?