Is this "hello world" program of yours interdimensional portal program to transport yourself outside of bounds of our time and space?? My mind is blown!!..
Your videos are a godsend for a fledgling games programmer who want's to understand the bigger picture and not just leverage what engines give you. Thanks so much, excellent indeed!
"So you can see, it doesn't look that great" - are you kidding me? This looks awesome. I would play the hell out of a fleshed out game with this aesthetic!
@@javidx9 I enjoyed your video, but I found a few minor issues with your approach. (1) Your logic is a ray-marcher, not a ray-tracer. Ray-marchers excel at very irregular shapes; ray-tracers are for regular shapes, like cubes and grids. That leaves a lot of room for expansion, e.g. columns which are really round rather than a fixed number of polygons. However, for the scope of this grid renderer, it's overkill. Criticism is easy, but improvement is hard. So I'd propose to compute how many times the vector has to be applied until it hits a grid line (not necessarily a wall), and then apply constant increments until it hits a wall. For example, if the ray vector is (0.8, 0.6), it would hit the next x grid line after another 1/0.8 = 1.25 vectors and the next y grid line after 1.666... vectors (nice decimals for an engine of a DOOM clone, don't you think???). The other coordinate is the index needed for the collision check in the map matrix. These tests could be run either concurrently (keep two running values and update the one that's closer) or separately (compute one, then check the other direction for accidental clipping, e.g. if you missed a corner). Actually, scratch the last bit; just do the one with the smaller increment, and if you hit something, check if the other direction hit something _just_ before that last increment. If you use the smaller increment, the index changes by one if at all, and there's no risk to miss anything in between. In any case, the grid lines are where the walls are, so two lines (or two columns) can cause a hit. (2) You had both an "emergency exit" based on depth (fDistanceToWall) and one based on x and y coordinates, and then a third one by putting #'s at all borders. That's overkill. You could use x and y coordinates alone; when a ray leaves the map, it won't come back. Or maybe only use the depth check if you want to render outside views of the map. (3) You can interpolate the vectors linearly from left to right edge; that avoids the "fish-eye" effect as seen at 23:02, which would be more pronounced in a graphics mode. Fewer sin/cos computations should result in a minor but measurable speed-up. (4) To detect the edges, I'd fill an array of "x , y , type" entries before drawing. x and y would be the respective coordinates of the feature detected, and type would be WallX, WallY, or null. I'd populate that array and then scan adjacent entries for differences. If two adjacent entries are different (e.g. different coordinates, as it happens from one segment of wall to the next, or different types of wall, as it happens at a corner), AND neither is null, I'd set the type of both to null. WallX and WallY would be drawn like you did, and null would be the variant with a black "wall" segment, i.e. nShade = ' '. That would eliminate the inner loop (the one that's run 3x3 times per column) and result in higher FPS. As an added bonus, the boundary is always 2 pixels wide, not sometimes 2 and sometimes 3. With REAL graphics, I'd use a different approach: compute the exact (x,y) coordinates of the hit (btw: one of them is an integer), and then compute the distance to the point where x and y are rounded to integers. That distance could then be used to shade the wall; if it's zero, black, if it's >=delta (a value around 0.02 should look OK), default brightness (modified by other factors like distance), and if it's
Really enjoyed this! I'm a second year Computer Science student at University right now. This amount of explanation was perfect for someone at my experience level.
Dude, you are RIDICULOUS! By far the best RU-vid channel I've seen. Thanks for doing this for us hobbyist-type programmers. Your work is very much appreciated!
For anyone who is strugling with not appearing wall after shading floor, just take a look at condition if(y < nCeiling) at time 24:20 it will be changed to y
Is that what it was?! I toyed around for about 10 minutes trying to figure that out. Noticed that change, made it, messed around some more, then it finally worked. I must have messed something else up in the mean time for that not to fix my problem. Also there were a couple missing includes: #include // for vector #include // for sort
Really?!? I simply changed nShade to nWallShade to shade wall and nFloorShade to shade floor and also got it to work. I figured it was nShade value confusion. 🤦♂️ still works! But your fix was way easier than my fix. You simply found the missing equal sign. I added in an entire extra variable.
37:00 i have noticed a little problem. the world is rendered in mirror image. if you turn right in the game and walk foreward, the map shows that you have turned left and walked foreward. i fixed it by changing this line 199 (line 257 in github) into screen[(ny + 1) * nScreenWidth + nx] = map[ny * nMapWidth + (nMapWidth - nx - 1)]; and line 202 (line 259 in github) into screen[((int)fPlayerY + 1) * nScreenWidth + (int)(nMapWidth - fPlayerX)] = 'P'; these changes mirror the map and they mirror the player's position on the map and thus everything works correct. btw it was a helpfull and great video
You are correct Damian, I think this may have been addressed elsewhere in these comments. A little mistake (out of many) on my part. I'm pleased you enjoyed the video!
You didn't fix anything. Your solution just mirrors the map, which is incorrect. All you need to do is replace cosf to sinf and sinf to cosf everywhere like so: playerX += cosf(playerAngle) * 5.0f * elapsedTimeCount; playerY += sinf(playerAngle) * 5.0f * elapsedTimeCount; if (map[(int)playerY * mapWidth + (int)playerX] == '#') { playerX -= cosf(playerAngle) * 5.0f * elapsedTimeCount; playerY -= sinf(playerAngle) * 5.0f * elapsedTimeCount; } It will render everything according to map.
I absolutely loved this. Elegant and wonderful solution, and goes a long way to demystifying some concepts that are very confusing for younger coders who are excited about game programming. I will absolutely share this with my students. What a great resource :) Thank you!
@@javidx9 Hi. My friend send me this video link . I subscribed your channel to learn from you. I love programming and am excited to have knowledge about programming. What IDE you used for coding?
19:17 I love it when he turns to the camera all of a sudden, explain and get back to work again. He is like "We'll need to do the same thing" and I cannot do anything but giving him a thumbs up because I have no idea about such coding yet it is fun to watch ahah
This is great work; I've been involved with software for around 20 years+, yet I still found your videos informative, inspiring, and mathematically thought provoking. I've been wrestling with the physics of racing for some time (having developed 3 crude simulations), your videos have just helped me develop some of these ideas. Would love a catchup over Skype sometime! Regards,
Hi Jamie, glad I could help! I'd love to make a really nitty gritty top-down 2D racing simulation game. I've some algorithms I would like to show this year about racing AI, but the list of things to make vids about never gets shorter :D I checked your Monaco GP vids - nice work! I don't skype, but you're more than welcome to come and chat on the Discord server, it supports DMs too if shouting in public ain't your thing - Jx9
When using std chrono for timing something like this, use steady_clock instead of system_clock. That way the user changing the date doesn't mess things up. From experience..
I wish one day I can explain myself as clearly and skillfully as you do. One of the best youtubers I've seen, and the best programming teacher too. You should totally write a book!
This is a more geometrically correct way of finding the size of the ceiling and makes the depth seem more realistic: int nCeiling = ((float)nScreenHeight - (float)nScreenHeight / fDistanceToWall) / 2.0;
SAme here bro. Fuck programming. It's hard. Might as well learn to cook food and learn how to dance like Michael Jackson. My wife and son might be even be pleased.
Iess exactly. It was difficult then because of the lack of how-to but still possible. I remember the tables of Turbo C/C++, good ol days.....now i feel old.
I don't study English well so it's a bit difficult for me to understand, but after listening to it a few times, I realize that you teach really clearly and easily!!! Much respecttt!!!
Thoroughly enjoyed it :) ! Thanks much for posting this great video... best part I like about it is the gradual progression/improvement you show (rather than showing off the final version of the game at the get go). And for anyone trying this out along side the video : kindly note that the sin and cosine are mixed up (this can be identified by thinking about where the origin is and where the X and Y axis are). Once you fix that, all the inversions/mirror image issues get sorted out. Also one thing I changed is to use unicode characters →, ←, ↑, ↓ - instead of just a 'P' for the player (this helps with orientation).
Love the random "clunking" around... Dropping stuff etc. Beautiful. Excellent video. You're a saint 😇 for putting this stuff together. Very useful. Thanks for posting.
Amazing video! When I was a kid in elementary school in the 1980s I loved the game "The Bard's Tale" but was frustrated that you could only turn by 90-degree increments and move by unit steps. I imagined coding up an engine like this to allow smooth movement and rotation, and asked my elementary school teacher to teach me trigonometry, because I knew it was required to do the geometry required for the 3D effect. Alas, my teacher said she didn't know trig.... How I wish I had access to this video back then! It's exactly what I had imagined doing myself on my Commodore 64, back in the pre-Wolfenstein days. I agree with you -- would be neat to see if this engine could perform fast enough on old hardware like that to be playable. Anyway, thanks for your videos and your clear instruction style.
I’m so glad I found this video. I’ve been doing C++ for quite some time now and I always wondered how to do some of the things explained so nicely in this video! Subscribed and liked!:)
I'd just like to say thanks again for this. As a project for myself, I translated this program to Python a couple of years ago. It taught me a great deal. I plan to go through this video with my son, who is starting to learn C++ for his degree program. You're an inspiration.
Fantastic video my man! You explained everything perfectly and I followed it to the end, I have no previous experience in graphics programming but this all made sense to me.
A command-line based 3D renderer is probably the only 3D renderer I'll actually be able to figure out how to make from scratch in the next 30 years. And it'll probably still take forever.
Around 10:40, since you're already calculating the sine and cosine of your angle to make the unit vector you might as well use simple trigonometry to get your distance to the wall. I would be something like (not actual code but you get the idea) distanceToWall = abs(playerX-wallX)/cos(angleBetweenRayAndVerticalAxis); //For horizontal (angle between -45° and +45° to the horizontal axis) rays or distanceToWall = abs(playerY-wallY)/cos(angleBetweenRayAndHorizontalAxis); //For vertical rays
Hi Ura I see what you are saying, bu the problem is I dont know where wallX and wallY are, unless I compute this for every possible wall and then sort them somehow. Also, wallX is not a single value for any given wall in this case. An alternative approach which is kind of similar to what you are suggesting is horizontal wall following, where you identify the wall on the left of the FOV, and then follow the geometry around until you fall of the right FOV. This way you can calculate the distances you need algorithmically, but you need to partition space in fun ways to make this optimal.
I've been looking for a video like this for so long! Thank you so much!! This explained and pointed out a good number of concepts that I was interested in.
Your content is so informative, challenging and inspiring. Thanks for all your hard work and keep them coming! If you are ever in Tucson, Arizona, I'll get you some authentic chile con carne on me!
I think this is a good way to teach people programming in C++, to get them into it a lot more easier, instead of throwing them a text book about C++ and having them read it and trying to figure stuff out. Giving them a project to work on to learn it by setting a task of making a 3D FPS simulation of some sort. Some courses of C++ tries to have the learner code a Calculator, which is fine and all, but it's too boring and can be complicated.
I love these. I'm going through every single code-it-yourself video and really enjoy figuring out ways to optimise and add new features to the projects. Such a blast :D thank you
genius work javidx9! this actually inspired me to set myself my own challenge to try and do raycasting in javascript using the html5 canvas. i suppose drawing on canvas some fixed-sized rectangles and shading them appropriately with respect to the length of the ray would suffice.
Thanks for taking the time to put this together! I learned a lot seeing this style of PFS rendering on such a low level as ascii. Very informative. Will let you know if I manage to get something like this working for myself!
When finding the corners, you could use the dist to integer of x,y instead. The corners are integers, so if the place where the ray hit is close to a corner, x and y should be close to a integer too. (x,y is the location the ray hit) the ray hit at x,y Tolerance is the radius of the circle around the corner. When inside this circle, you hit the corner. DistX=min(x - floor(x),ceiling(x) - x) DistY=min(y - floor(y),ceiling(y) - y) If DistX
If this guy can create a prototype of FPS game in a terminal, I can succeed in creating my textual adventure game in terminal, actually in development.
@@theali8oras274 Well, for the moment, it's still in development and I'm focusing on core functionalities (command prompt, menus, inventory, moving from a zone to another, quests system, etc...) but I'm willing to distribute my game under GPL license, so I could tell you when it will enter in Beta phases, which won't be for now... ;)
I started on this project. I got to rotation before stopping. I was in a long car ride and I open up this project and realized I did not have rotation. I do not know much about sin, and cosin so I just winged it and after 2 hours I have rotation with player movement working together. I had a long 75 lines of code just working out a xModifier, and yModifier to multiply my X and Y movement by. These combined always = 1. WHen I got internet back I looked to see what he did in the video, and I was so sad when I saw it was a sin, and cosin. : ( But I am so Happy about figuring out how to do it by myself!
3:30 "... cause i don't want this video to be half an hour". No. Actually, it's 38 minutes... :) Anyway. GREAT tutorial. :) I am trying to code this on a C64.
6502 assembly is quite limited, too. No sin, no cos, no sqrt in hardware, no FPU at all, heck, not even an integer MUL command! 6502 is basically "Library or f### off" ! At least the CPU is faster than the clock would indicate; 3Dungeon crawl in text should be doable on C64. And by now there's probably a decent C even for C64. If there's not, there should at least be a free one for PC with C64 output.
I would have done the edge detection a little differently: by remembering which block index (or empty) I hit the last time. If I hit another one or empty next, I know I am at a border. Very interesting this 2D game spaces! Thanks for explaining that very well.
Hey thanks flightvision, this is true, but will only give you very thin borders between adjacent blocks that do not scale with proximity. This may look good if you are cel shading the maze, or be useful for secondary processing stages though.
:D It's funny, I do have something in the pipeline for this project. Speaking of tetris, your video has inspired me to try a tetris with the smallest memory footprint I can get - basically I'm reducing it all to bitwise ops and karnaugh maps. It's proving to be a great example of information theory, as I compress the tetromino data, naturally, the program expands. Too early for a video yet though.
javidx9 The program expands with C, but in a language with bit access it should be under 1k, and monochrome video memory can be the game memory. I did port a bit access Tetris fully this time, but it was too large. The complete C rewrite used little program memory and huge RAM :D the game memory is ASCII like yours, and the mono video memory is extra, such as with your PC, there's probably some huge video RAM busy representing your ASCII on a hi res monitor :D
I think fEyeX and fEyeY are better explained as decomposing the player's direction into x- and y-vectors. They're not necessarily unit vectors unless we assume the player's direction vector also has magnitude 1, but there's no use in defining that so it's just complicating things. Anyways, this tutorial was great fun! Short and light on dependencies enough to be easily followed by beginner/intermediate C++ programmers, but also results in something quite interesting. So many tutorials out there make you go through hours of copying code only to arrive at something unimpressive. Good job
Please correct me if I'm wrong, but I think the angle rotation direction at both casting the rays and turning the player is not correct. As the player angle increases (17:25 lines 56-60), the player turns counter-clockwise, and when the screen is rendered from left to right (9:18 line 53), the apparent direction is clockwise instead. However; as the rays are projected with increasing angle (9:18 line 56), it is calculating the map counter-clockwise, thus canceling out the error, but leaving turning the player into visually correct direction. The map and the player view don't agree, as seen in for example at 37:08 where the player passes the walls from the left side, but on the map the P passes the #'s from the right side - and that was the thing that caught my attention.
Hi Matti, you are kind of right. I believe the map is actually either rotated 90 degrees or reflected in x=y, from the player, and as you say, it all cancels itself out. Primarily, this is because I use sin and cos, instead of cos and sin :D So yes there is a discrepancy in there. But that's incredible attention to detail you have - thanks for highlighting it!
I dumped the source code into Processing Java programming environment and with a couple of minor tweaks, got it to work flawlessly. I am now trying to port it to ZX Spectrum basic.
For the WriteConsoleOutputCharacter() function it says "expected an expression" before the coordinates. Edit: I loaded the project in 2017 instead of 2012 and it fixed it. 2012 must have some incompatibility with some updated windows functions.
your tutorial is very good. i wrote it in python but yea the performance wasnt very great. c# alr worked better, but now im doing it in c++ to learn the language a little
This just showed up in my suggested list, so I might be a little late to the party. :-) I just wanted to add some (constructive) criticism. The first thing that immediately caught my attention is that your code is prone to the fish-eye perspective problem that most trivial ray-caster fall victim to. You can see it when you are looking straight at a wall. The wall is closest right in front of you but as the angles increase to the side of the screen, the distance of course get longer so you draw them smaller. To fix this you should use the 'player coordinate-system orthogonal distance' to the wall. Simply use the dot-product of the casted ray with the unit vector of your viewing direction vector. This way such wall would be at the same projected distance and will be drawn all with the same height, thus avoiding the fish eye perspective. Let's try some ASCII art here for the wall I was talking about Your code: __...------...__ --...______...-- wanted result: ---------------- ---------------- My second remark: I like the creative solution you use to find the border, but in effect there is a much simpler way. When you look up if the map has an # in it you use the integer part of x and y coordinates and simply ignore the fraction part of those floats. You could however use this fraction as an indication of where the ray hit the boundary of the square on your map. You need to take small steps or do some simple linear interpolation to get your x,y coordinate to fall right on/close enough on the squares edge. If you do this you can use the fraction part to look up the column of a texture wall for instance. Or in your example if (fractional-part-x 0.95) and (fractional-part-y 0.95) then bBoundary = true
Hi David, All feedback is appreciated. The fish eye effect has been discussed by others on this comment thread so I wont dwell on that again :D. Your method for boundary isolation is valid, and in fact was my first attempt. But what I found was having the hard constraint like that gave aesthetically undesirable results, walls that were close up look just fine, better even, but walls further away often were not delineated. I suppose the solution is to factor distance into those hard constraints with some 1/distance bias. Anyway, excellent feedback, thanks!