It was done entirely in CS2D. Just watch the video.
Bonus video
Introduction
The first mention I read about "bad apple" was in a Russian games magazine "Igromania" (Game-mania). A small column, a few lines of text were dedicated to a recreation of Bad Apple animation in WarCraft 3 using the in-game map editor. I had literally NO IDEA what this "Bad Apple" was all about and I only knew WC3 because it still was a trendy game (and esports) at the time. Yet the little black & white thumbnail of Bad Apple stuck into my head.
It was only years later that I had found the original on YouTube and watched it - no wonder it has become popular because it really is a masterpiece! There're song covers in many languages, there's an osu! map for Bad Apple and there are of course more fan-made recreations in games, the CS2D version now being one of them.
How did I come up with the idea to do this in CS2D? There must be an inspiration or purpose for everything we do and my inspiration was the Darude Sandstorm in Factorio video. Usually Youtube's recommendations suck, but this time they had helped me
So, I had an inspiration, a memory from the childhood and my favourite game at hand. There's no way I wouldn't try to replicate it in CS2D. And I did.
Every idea starts little
Luckily, I can play this creative game called "programming". The goal is to somehow render video frames in the game. However, CS2D has A LOT of limitations and the biggest one to overcome is
the screen size - you only see 20x15 tiles on screen
20x15 tiles? That's just 20x15 PIXELS! It's nowhere enough to create a picture!
We need to go BIGGER!
Screw that. 20x15 pixels is for n00bs. We are f***ing evil genuises here and will not stop. How do we make more pixels out of that? Right, launch MOAR cs2d windows.
Many people don't even know that it is possible, but the game window you see doesn't need a surrounding border at all! This way we can disable the window borders, move different cs2d windows close to each other and it will look like a huge single game window that we can draw pixels inside. (that window mode is called "borderless", maybe you have seen the mode "borderless fullscreen" in some newer games).
At this point I had to do some easy peasy math. I have a 1920x1080p monitor and by experimenting I found out that I can reliably run up to ~28 copies of the game. The Bad Apple video has a 4:3 screen ratio, same as the the old cs2d version 1.0.0.2. This results in a 5x5 game window grid or 20x15 * 5 = 100x75 pixels. Now that's something we can work with!
To accomplish that I wrote a script in AutoIt that would start 25 cs2d windows, move them to a correct position and then they would connect to the server as players. Our canvas for drawing is ready!
One thing that I didn't expect to happen is that my computer's performance was not enough to handle all those cs2d windows Despite having a brand new top-end CPU (AMD Ryzen 1700, 8 cores at 3.7 GHz ) it was hitting ~100% processor usage. There's a bug within CS2D: the game will need much more CPU resources when the game window is not active. And yeah, I had whole 25 windows inactive... I could somewhat reduce the negative effect by lowering graphics settings of the game and turning SMT back ON (like Intel's HyperThreading, 8 cores will have 16 threads to execute).
Anyway, it worked. Let's proceed to...
The Lua side of things
CS2D uses the Lua programming language to script mods etc. It will be running on the server and tell our little player-windows what to do.
The most important part is to spawn each player at a correct position, from top-left to bottom-right corner.
I had to disable mp_floodprot and mp_idlekick, and set mp_maxclientsip to 32 - do you remember that we have 25 windows that must connect to the same server? The last part of our masterpiece is to disable the in-game HUD (Health, Armor bar etc) with mp_hud = 0. I think many of you also didn't know this option existed... that's why: read the documentation! It's like googling for answers but for smarter kids.
Next up is the hardest part. We have a 100x75 map size, but how are we going to draw the pictures?
The boring way is to save the video as single images and show the whole image to a player (640x480 window resolution) - but it sucks, because it's too easy. We would not need 25 windows for this. Everybody can do image. We are building a Rube Goldberg machine here (a very complex machine to do something simple)!
Another way is to use settile to change individual map tiles. This way we're limited to only 255 different colors (tileset limit). It's good enough for Bad Apple because it's a black & white animation anyway. That's what I have chosen.
Third option is the same as with settile but with images that act like pixels. Advantages: more pixel colors and higher video resolutions are possible. But I disliked the idea, because it's not like the true way of recreating Bad Apple with in-game methods. Everybody can change the image size, again, it's too boring for me.
I have worked with settile since the beginning of the project. The first thing I did was to draw player ID numbers on the ground where they spawn, so I know that the game windows are lined up (placed) correctly. You can see the numbers in the video, from 2 to 26.
Next I wanted to test drawing performance. How many tiles can I change each frame at 50 FPS? The result was disappointing (bad). With 25 players and 50x20=1000 tiles per frame the players would immediately lose connection to the server and timeout. Even though the server was still running, it was VERY annoying, because it takes 6 minutes(!) to (re)start all 25 player windows.
Through further experimentation I found out that:
the draw limit per frame is at ~820 tiles
the dedicated server works even worse than "New Game" server.
The dedicated server would timeout players immediately with "MAX RECONNECTIONS ATTEMPTS 20 reached" whereas the "New Game" server would have no problems.
All these bits and bytes
If you ever have watched a video on Youtube (and I certainly know you have, at least since you have watched the CS2D Bad Apple) then your browser has read and understood one of the most common video codecs and containers. Youtube now delivers .webm (VP9 codec) and .mp4 (H.264) for video. Your browser is smart enough to play them, and our simple Lua script is definitely not. Lastly, it's not easy to write a script for a video format.
How can we make our job easy? Give our Lua script something it can understand! We can convert the video into a series of uncompressed images: Bitmaps (.bmp). Lua will happily eat all of them bitmaps Since they are not compressed and an easy format, the script for reading bitmaps is really simple and I have instantly found one on the Internet.
I've used some command-line magic to convert the video using FFmpeg. If there's a video format that FFmpeg can't understand then nothing can. It's a damn Tyrannosaurus Rex of Video.
FFmpeg has converted the video into 6571 images. Also we have created a color palette that was used for video convertion. We will use this color palette as the tileset for the map - each image pixel now has it's own map tile friend
Strength lies in calmness
Ok, we can now successfully read the images and draw correct pixel colors on map (the pixel color -> tile mapping has took me a few tries of trial&error, because I'm so lazy... )
I first wrote functions to draw a single image on map and then on top of that, a continious loop that would draw consecutive (next) frames when the last has finished.
One optimisation I made straightaway was the RGB_COLOR -> #TILE lookup. Without knowing the actual values for Red, Green, Blue, the script would instantly know which tile it's supposed to represent and draw it.
At this point the first speed results were in:
98 frames drawn in 51.5s
51.5 SECONDS, CARL! That's just 01.9 (one point nine) FPS. Oh my God, that's slow. I feel like a pwned n00b now.
Don't give up though! The biggest improvement came from sending the settile command in just one go. At first I used one call of
parse("settile X Y 123")per pixel, but it was SO SLOW that even a turtle would beat it at moving pixels. As you have read above, up to 800 tiles can be changed at once and that's what I did. For the game it looked like a freaking mother of text of a command:
1
"settile 27 65 239;settile 29 65 154;settile 31 65 3;..."
Another change of the same type was to "cache" current map pixel status. I mean, you don't want to do homework in the evening to find out the next day that the lesson was cancelled. Likewise here: we don't need to send a settile command if the pixel didn't/won't change.
I did some tweaks to the amount of drawn tiles per in-game frame and increased the speed to 2.9 FPS (~33.7s).
Any other tweaks within Lua itself that I tried didn't help. In conclusion: The communication between CS2D and Lua was slow, but not Lua itself. Nothing surprising, actually.
I can see light at the end of the tunnel
Here's a list of what's working:
25 game windows + server
Image pixels drawn as tiles in-game
2.9 FPS that will be accelerated in the final video
Recording tool
Everything's ready, let the computer be a computer and record the video while we're afk-ing.
Better luck next time!
It took an hour(!) of in-game "rendering" time. During some scenes, that had many changing pixels, the image looked distorted due to high CPU usage. The game windows wouldn't update all at once - some windows had an older frame and the others already updated to the new one.
Another "feature" you can see in the image above are the actual players. I thought equipping players with stealth armor would be enough to hide them, but it wasn't. I ended up using the new mod feature to make fully transparent Terrorist skins. The players were fully invisible now.
I reduced the playback speed and gave it another try. 2 Hours now... such render, much fun. I was surprised when I came back: One of the game windows had CRASHED in the middle of recording, EXCEPTION_ACCESS_VIOLATION. Well... F* You Too!
However now I had a chance to change the converted images: the edges looked over-sharp-ed because of the resize method I had used. "Lanczos" gives poor results (in my opinion) in some scenes by oversharping the edges. I reconverted the images using the bicubic method and I liked the output.
Next try! And guess what? One of the game windows had crashed once again! Thanks, Obama!
Finally, it took me 4 tries to record this video.
Finishing touches
In the process of speeding it up (to make it the same length as the original video) I learned that there's no interpolation of frames for timelapses (and it is kind of a timelapse).
Initially I thought: Let's do the recording as slow as possible, then there will be very few image artifacts and the final video will have high image quality, because we will use many "good" frames for interpolation and less "bad", distorted frames. There seems to be no such thing.
The interpolation is used to convert e.g. 30 FPS -> 60 FPS video, but no one does it the other way around. If you need to put a 9000FPS into 30FPS video, then every 299 Frames will be skipped/dropped and the 300th frame picked up. If this 300th frame is full of artifacts then you are out of luck, and this shitty frame will end up in the video.
What now?
I could record more popular videos in CS2D, but there's virtually no more purpose. I had fun. I practiced some Lua and coroutines, learned more about CS2D: Weapons have limited range! I even managed to visualize a laser shot's length. Moreover, there're many bugs within CS2D (and one in Windows' Desktop Window Manager - the startup time of 6 minutes...) I have managed to figure out the most nasty cs2d bug, the one with connection timeout.
Finally, I added an outro screen with the request to vote for CS2D on Greenlight. At least something practical, although CS2D seems to do good compared to other Greenlight titles. Nonetheless, I think CS2D needs more votes than a regular Greenlight game just because of the name and the fact that it used to be a Counter-Strike clone in 2D.
Share the video and thank you for reading!
If there's public interest, I will publish the rest of the scripts and a howto on Github (right now there's only the server-side Lua script). And just keep in mind that your PC will most likely not able to handle it
I tried to use simpler English on purpose, e.g. word (explanations) - we have many huehuehue-friends here who don't speak Ingles very well.
Sincerely, VADemon.
PS: DC any greater amount of text looks miserable. Bigger css:line-height and possibly text-size are appreciated.
PPS: I am tired of writing. I can haz f00d finally!
MISC:
Image album: https://imgur.com/a/1ppsC
CS2D Bad Apple video: https://youtu.be/YsRol0sV754
Bonus video: https://youtu.be/ataoxr3W4tE
Repository (incomplete): https://github.com/VADemon/cs2d-script/tree/master/badapple
---
TODO: Report the networking bug, list of bugs
edited 7×, last 10.05.17 02:01:32 am