This video demonstrates how to build a game renderer using TypeScript and Bun, where the renderer acts as an abstraction layer between game logic and the canvas API. The renderer interface defines methods for camera management, clearing, and drawing shapes (rectangles, sprites, text) with options for rotation, anchor points, and alpha. A fixed-timestep game loop is implemented using requestAnimationFrame to ensure consistent game physics independent of frame rate, with delta time calculations for smooth animations. The setup includes HiDPI support through device pixel ratio handling and a clean project structure separating server and game code.
TypeScript Game Engine: Canvas Renderer & Game Loop Implementation
Added:All right.
I'm wanting to kind of start a series where I create a game. We're going to use TypeScript.
Um and we're just going to kind of go one step at a time and see where it takes us. I know I want it to be a 2D game.
Maybe isometric.
But um yeah.
Let's uh Let's just get going and see what where it takes us. So, first thing I like to and I like to use Bun.
And I found that if we just create a blank React project it's a good place to start and we can kind of just clean it up.
Um so, as you can see if we run Bun dev and we can navigate to localhost 3000 in here.
That is like the initial setup for a React project. But we're not going to use React. We're just going to use it as a kick start or uh a kick start for our project. So, we can do a few things.
We can come into some of our uh files and start modifying them and getting rid of the TypeScript stuff or the uh uh React stuff.
So, we're just going to come in. I'm just going to get rid of that.
We'll rename this to All right.
game Let's just We'll call it game series.
And we'll restructure a little bit of this as well.
Um I'm going to get rid of all of this.
Our CSS we can get rid of.
And we'll move a lot of this into the HTML here.
And we will swap out this for a canvas element.
Uh canvas ID equals game canvas like that.
We'll keep this.
We'll move these two outside.
And we'll move We'll create a new folder outside of here called server. And this is where we'll throw the um the like dev server from Bun.
And that can get modified as well.
We only need this one uh route.
And then we can get rid of our logos here.
We'll throw a new file called main.ts in there.
And here this isn't going to be TSX anymore.
It'll be just TS.
We'll give it the correct title as well.
And we can get rid of a lot of stuff from the project. So, we don't need that.
We don't need a bun.toml for this project here.
And then this is going to change drastically.
We're just going to import main like so.
And I think what we do is uh We'll say import.meta.hot.
accept. I think that's really all we'll need in here.
And then we can Let's just put something in the body at the top.
Um Actually we'll say canvas and let's just draw a circle or a square in it.
And we'll We're going to be using TypeScript, so let's just set it to uh green and we'll fill a rectangle. Let's put it at uh We'll set these things, I guess.
canvas.width canvas.height And then canvas.width / 2 and canvas.height / 2. We'll put it right in the middle. It'll Actually, it's going to end up being It'll be bottom.
Should be. We're just going to be checking make sure that this all works.
So, then we'll get rid of this here.
So, our source should be Our source directory will be nice and clean.
Um I think that's really good.
Let's see.
We need to have And we need to change one more thing.
This is going to be server/ Actually, index.ts.
Rename that. So, we're just getting this set up.
And front end That looks right.
We just need to rename this to TS.
All right.
And we we should just put this at 50/50 for now. That's a little square there. So, we've got We've got it set set up.
So this is kind of just a way to kind of structure and get things started for the project. Let's also delete this.
What we'll do is we'll go one step further.
Delete the node modules and run a bun install.
That's just going to get us only really bun uh and have access to its types and such.
So, we should have a much cleaner project and a good starting point.
So, where do we want to begin and what do we want to focus on?
I think that the first thing I want to do, because I want to make an ECS and I think that I want to have a clear way to communicate or to interact and render things.
So, let's work on that first. So, we'll create new folder.
Call it renderer.
And in here, we'll create renderer.ts.
And so, basically, before we draw anything the right way, we're going to create a the renderer.
Uh it's kind of like an adapter that hides the drawing back end.
So, the ECS game code will say what to draw uh and the renderer decides how to draw it.
It doesn't really care um about what the underlying tech is. Uh the game won't care. It'll just know how to interact with the renderer.
So, I guess that's the first thing. So, let's separate create the uh create the renderer to handle like the the camera transforms, clearing, being able to write text, um draw sprites, draw like a rectangle.
Um and then we'll have the rendering system once we get to that, get all of the information that it needs to then talk to our renderer.
So, the first thing that we'll probably want is to set some types. So, this will be what we do at the top. So, we'll uh create a camera 2D.
And right now, it's only going to have an X and a Y.
And then we'll have color, which is a string, and that will be anything. We'll say uh RGB, RGBA, X, HSL.
HSLA. Oh.
HSLA. Yeah.
Um So, we also should probably put a zoom.
Zoom, which is number.
One is 100%.
So, the camera uh is the world space position plus a zoom, and the color is just a CSS string that uh keeping things tidy makes them easy to swap out in the future. So, um so, now we need uh the rendering interface. This is uh just a description, something that the game engine itself will be able to utilize. So, uh it'll be an interface here, interface renderer.
And so, we can describe what a renderer has and does.
So, I'll have a read-only width and read-only height.
And then we'll have some methods, set camera.
We'll take a camera.
And actually, it won't require all every parameter. So, it'll say partial.
Oops.
And that way, we don't have to set every part of the camera with uh when we call this function. We can just update specific parts.
And we can get the camera, which obviously is pretty self-explanatory.
We want to be able to resize with a width and a height.
And we want to be able to clear, passing in an optional color if we want.
We want to be able to draw a rectangle with X as a number, Y as a number, width, height, color, and we'll have some options.
Our options will be rotation, anchor, which will be the anchor point it from zero to one, kind of like UV coordinates, uh where we're going to uh assume its center is.
Um we could put an alpha, maybe.
And smoothing.
I don't think we need smoothing. That's I think alpha's fine there.
And this will be a So, we'll also allow us to draw sprite, which is a tech.
Uh it's actually going to be an HTML image element. I wonder if we can just say image.
I think that it needs to be HTML.
Or or canvas element.
We'll have Let's uh Let's this for now.
We'll have the X, which is number, Y, number, width and height. And this is going to have the same, but here we can do the smoothness.
Or whether we have smoothing or not.
And then we'll do the same thing. We'll be able to draw uh text to the screen with a string, X, number, Y, number, options.
We'll have color, size, font, well, alignment, and it'll be actually canvas text aligned.
Baseline, alpha.
We could put a rotation on there, and we could probably you do the same thing with the anchor.
And we'll have a get native, which will return the uh the actual context.
So, it's access to the underlying context rendering context.
Okay.
So, uh we need to create a factory to create this uh got this uh renderer.
So, export function but make canvas renderer.
And that's going to take a canvas.
We'll say context ctx.
Canvas rendering context, it'll take a canvas.
An HTML canvas element.
Let's actually rethink that. We don't need this.
If we can just derive it from that.
Options.
This will be width.
Number.
Height.
Default font.
String.
Uh we can set our image smoothing.
Image smoothing.
And our camera mode.
And that is going to be top left or centered.
You'll see how this comes together.
And this is going to return our actual uh configured renderer.
Um So, let's see.
Let's start building the actual internal state. So, we'll put width equal to options uh width or canvas.width.
And we can set context right away. const ctx.
Yeah, there we go.
And then height camera mode And then state camera 2D And we'll set its initial state.
To wand.
And then ctx.imageSmoothing, we'll set that enabled by default.
We can set the default font.
And let's do mono.
I think mono space.
So, we're basically setting all this stuff up so that it is set only one time.
We don't have to uh we don't have to like set these values every every render or anything.
So, we can create a function called apply camera.
And this is internally used. So, we're not going to worry too much about the type safety.
So, s- we're going to set the transform to reset everything when this is called.
And we'll check what the camera mode is.
And if it's top left set the transform state.zoom [Music] And these are just some settings for the uh context set transform. It's really not necessary to fully grasp it.
We're just setting all of the transform information based on the camera.
And then we can say else bullets is width times 0.5.
Or y is the height. So, this is the origin.
And that should be good there.
We need to create a function called clear all.
And that will take an optional color.
We'll save the current uh state of the canvas or the context.
And we'll set reset the transform back so that we can clear the full screen.
So, if there's a color ctx.clearRect else we will first set the fill style then clear.
And then we'll restore and reapply our camera.
And then we can actually start off by applying the camera here.
Let's see. We can return now our actual API.
So, we'll get we'll get some method the methods that are required.
Yep, we're just filling out these the uh initial rendering type. We're filling out all those methods.
So, we'll have set camera.
And we don't even need to pass these types in because we've already set those.
If And we'll actually take all this from here.
So, if partial.x We're just set- setting that and reapplying the camera.
Get camera will return the state.
And we'll be implementing resize here with height and then we can set those global values or uh scoped values.
And we'll set the canvas. We'll do all that and apply the camera again.
Here take the color and that will just run the clear all.
And then we will create our method for drawing the rectangle here.
So, we'll first save the state is it context?
We'll check if uh options.alpha alpha is not equal to one and Let's say Yeah.
Let's say undefined or one then Let's see CTX.fillStyle is equal to color.
CTX dot Uh let's say if options.rotation Let's actually we'll start with the options.
anchor And let's fix this error.
And if rotation We're just going to basically rotate.
And then we can restore.
We're going to do something very similar with the sprite.
X Y width height with options and because we're doing so much the same we'll just kind of get this out there.
And yeah, you can see it's essentially the same but we're just drawing the image uh after translation and rotation.
Now, one thing I Yeah, we've got that up here.
And we'll do our text as well.
So, again, that's going to be a a lot of the same.
Did we allow for rotation?
Yeah.
So, we should do the same stuff.
If options do that.
We'll We'll base it off the font that's set and the text alignment.
And it will translate it.
So We're going to let the AI kind of brute force the boring stuff here.
If we have our rotation then we're going to rotate fill text else we're just going to fill the text and restore.
And then that native is just going to return CTX.
And I think that's it.
Instead of returning here, let's say past API is a renderer.
And at the end that's where we'll return the API.
Okay, so this allows us to interface with our uh canvas and we'll be able to build on this. We could even swap out the renderer at some point.
So what we can do with this now is we can try to test it out.
So let's get rid of this code here.
Actually, we'll get rid of some of the code.
We'll just keep this here.
We'll import that. So, import make canvas renderer and I'm going to keep this like so.
So, now we've got our canvas here.
And we can set a few things. So, let's say we'll set the CSS width equal to 800.
CSS height 600 uh let's do 450.
And we'll set the canvas styles to that.
Yeah, so we're going to give them a fixed size so that uh we have a stable coordinate system.
And it's going to be centered in the middle of the screen with our CSS or with our styling. So um we'll set the DPR math.max one window.device pixel ratio.
Or we'll assume it's one.
And we can then set the uh canvas's width and height there.
Now we can actually create our renderer.
And it will take a camera mode.
Top left is what we'll do.
And we will run renderer. dot set camera.
And I think zoom will be DPR.
All right. So, we built our camera renderer, our canvas renderer.
We're using top left camera.
Uh we'll set the camera uh And set it to the DPR. That maps one world unit to one CSS pixel.
Um uh That's why we're still drawing at full resolution.
So, now we could actually start doing something with it. So, we could just create some temporary stuff here, too.
So, we'll create a box.
X will be 50.
Y will be 50.
Width can be 64.
And height 64.
And we'll give it a velocity of 120 on the X.
And now we can just kind of build this loop that will re-render stuff.
So, do we want to try to draw first? Let's just draw.
Uh renderer.
dot clear.
And then renderer. rect.
And let's see what this does here.
Fun dev.
So, we've created a a square.
Now, let's do something fun with it.
Let's create a function called frame.
And above it, let's get last performance. dot now. So, we'll set a time.
And the frame will take a number called now.
And we'll derive our delta time from that.
And this is the seconds since last frame.
And then we can reset last to now.
And we'll run some updates. So, we'll say box.
x plus equals the box velocity times the delta time.
And we'll do a check to see if it's off the screen or not.
So, box. x plus um We'll center it.
Box.
dot width.
Let's check if Actually, we'll do box. width divided by two.
And you'll see why in a second.
Um If it's less than zero.
Or if box. x plus width divided by uh oops. Box.
width divided by two is greater than CSS width.
Then box.
vx times equals -1.
Oops. Here we want this to be times.
We're going to revert We're going to uh invert the velocity on the X.
But we also want to set We're going to reset uh the box's position.
And then we can render. Renderer.
dot clear.
Renderer. rect.
And we don't have a center just true, so I don't know why it did that. But we do have an anchor.
Which can be zero.5 and 0.5.
And then we can request animation frame.
And then to initiate it, we do the same down here.
And we've created a bouncing box. And it should bounce left to right.
Cool thing is we could have rotation.
And we could set rotation to the box's rotation.
And rotate box.
So, now it's rotating. And since we've anchored it to its center So, now we have a bit more uh We have a bit more fun.
Um we could also Let's see.
Let's grab an image.
Let's just create an image instead of a box here. We'll use the box though um for state, I guess, or to continue to rotate it and do all the same work though, but we'll change out to the render function.
So, say const uh let's call it an icon is equal to a new image.
And we'll say uh icon.source is equal to this logo here.
And then we'll set set can render.
And put this above that.
And then we'll set can render to true.
Okay.
Then we'll just hot swap these.
We'll say if box.can render and we'll render that sprite.
Else we just render the box.
That way this should swap out.
So let's see.
This should return an I think that's right.
Uh let's see.
And the only thing I could think is that we might have some problems loading that. Let me check in the browser.
So let me save this instead.
So I'll Gotcha. I just it's not the right file.
I just grabbed the wrong link, I believe.
Let's try this here.
Okay.
So, we've got way too big of a width here.
We don't want to change the width and height.
There we go.
There we are. So, now we are drawing an image.
Seems pretty straightforward.
We can even set our V.
And down here I'm going to have to figure out the runtime issue that we have.
Cuz it doesn't want to render sometimes.
Um yeah, so then we can actually set this to box.rv.
And we'll do the same thing here. Box.
rv.
Let's set its Y at Yeah, we'll have to figure that out.
We could always wrap this.
This might do it here. This might help.
And then if we go to this I wonder how that would work.
I think that's main problem.
Now, I wonder if we edit main >> Let's see. We'll Let's figure out why this happens. We'll see if we can fix this.
Cuz we want to be able to uh dependably restart.
I think I'll figure that out, and I will see you next time. I think we're going to work on the uh start of the entity component system.
And we'll go from there.
Up Next

Cerebral Cortex Structure and Function | MCAT Biology
@khanacademymedicine
752.4K views•2014-04-25

IFS Therapy Demonstration: Complete Session with Unburdening
@IFSCA
95.9K views•2021-01-13

FastAPI vs Flask vs Django: Choosing the Right Python Web Framework
@TechWithTim
302.5K views•2024-05-26

Game of Thrones Opening Credits: A Cinematic Analysis
@gameofthrones
46.3M views•2011-04-18
Related Study Plans & Knowledge Roadmaps
Structured learning paths in General & Interdisciplinary Studies










![Bun Tutorial – JavaScript Runtime (Node.js Alternative) [Full Course]](https://i.ytimg.com/vi_webp/eTB0UCDnMQo/maxresdefault.webp)





![Making a game in C from scratch! Ep 01: [Platform Layer and Software Rendering Programming]](https://i.ytimg.com/vi_webp/cTwh9oHcs1w/maxresdefault.webp)
















![[ Javascript / HTML 5 ] Membuat Game Platformer](https://i.ytimg.com/vi/Z3f86mI0SUc/maxresdefault.jpg)





