"Cat-400" (c4) is a game framework for Nim programming language. Being a framework means that c4 will do all the dirty job for you while you focus on creating your game. Under active development.
C4 is being developed for custom needs. Please note that it's more educational project rather then professional software, however if you like it you may use it and make contributions.
The main benefit of c4 is its documentation - I try to make code and docs changes side by side, so docs should always be up-to-date. This is the thing many projects lack.
C4 tries to be clear and well-designed. Your contribution is highly appreciated! Sometimes even an idea or better design/implementation would be very helpful. Check out current issues.
Learn by coding your first game with c4! Less words - let's try to build something.
First, install latest c4 right from github:
nimble install https://github.com/c0ntribut0r/cat-400@#head
Create test project.
mkdir /tmp/test
cd /tmp/test
touch main.nim
Now edit main.nim
. Let's just launch plain c4 without any custom code.
from c4.core import run
when isMainModule:
run()
Check whether you can launch c4 and show version:
nim c -r main.nim -v
...
Nim version 0.17.3
Framework version 0.1.1-12
Project version 0.0
Our main.nim
looks empty, but the main job is done under the hood when calling run()
. C4 initializes loggers, splits process into client and server, launches infinite loops and does many other things. We don't have to implement it ourselves which lets us focus on really important things! You may have a look at options which are available for your app by default:
./main -h
Now let's start customizing.
Configuring c4 is nothing more than changing a tuple. The framework uses some reasonable defaults for your project's config (like version: "0.0") but sometimes we'll need to change them. Oh, let's start with version
:
from c4.core import run
from c4.conf import config
config.version = "0.1"
when isMainModule:
run()
Now nim c -r main.nim -v
will say that our project version is 0.1
which is better than default 0.0
. Well, now you know almost everything you need to create your game.
C4 uses client-server architecture. This means that unlike other nim game engines, c4 launches two separate processes (not threads!), one for client and the other for server. Client does all the job for displaying graphics and UI, playing audio and reading user input. Server does the real job - it launches world simulation, handles physics, processes user input etc.
Important fact is that server is always launched. Even if you play a single player mode, your client is still connecting to a local server on the same machine. If you connect to remote host, you may use your local server for client-side prediction (which is an advanced topic).
C4 allows you to launch your app in a "headless mode" - just a server without a client. This is useful if you want to launch your custom server on VPS or so and you don't need the client at all. We will also use this mode during first steps so that we don't have to care about the client. Use -s
flag to launch server only. You will see something like this (output may vary depending on c4 version):
nim c -r main.nim --loglevel=DEBUG -s
...
[2018-01-10T21:48:07] SERVER DEBUG: Version 0.1.1-19
[2018-01-10T21:48:07] SERVER DEBUG: Starting server
[2018-01-10T21:48:07] SERVER DEBUG: Server is Loading
[2018-01-10T21:48:07] SERVER DEBUG: EnetNetworkSystem init
[2018-01-10T21:48:07] SERVER DEBUG: Server is Running
Quit with ctrl+C
. Note that we passed --loglevel
flag to the executable so that we can better know what's going on under the hood.
We haven't defined any specific behavior, so server just runs by default. Now it's time for "hello world" program!
States are something you'll find very helpful while building your app. Explaing what State
is would be a redundand job - just go to excellent Robert Nystrom's website: http://gameprogrammingpatterns.com/state.html.
C4 relies heavily on states. You will see (and hopefully use) it very often. Let's see an example right now.
C4 has a Server
object. Let's omit its internals and just focus on its state
property:
type
Server = object of RootObj
state: ref State
# ...
Server may be in several reasonable states, like None
(unitialized), Loading
(initializing internals), Running
(running subsystems) etc:
type
None* = object of State
Loading* = object of State
Running* = object of State
Each state not only represents what an object is doing, but also allows to perform some state-related actions. For example, when Server
enters Loading
state it initializes all its subsystems; when Server
enters Running
it launches infinite game loop.
Now let's be destructive and make server just output "hello world" instead of launching that boring game loop! Create new folder for server-related code:
mkdir server
touch server/states.nim
Edit states.nim
and define a transition to Loading
state. Transition is defined by switch
method like this:
import c4.utils.state
import c4.server
from logging import nil
method switch*(self: var ref State, newState: ref Running, instance: ref Server) =
# this method will shadow default server's one (which is not a good idea)
if self of ref Loading: # if we came from Loading state
self = newState # actually swich current (Loading) state to Running
echo("Hello world")
If we now compile our code we'll see no changes:
nim c -r main.nim --loglevel=DEBUG -s
...
[2018-01-10T22:46:53] SERVER DEBUG: Version 0.1.1-19
[2018-01-10T22:46:53] SERVER DEBUG: Starting server
[2018-01-10T22:46:53] SERVER DEBUG: Server is Loading
[2018-01-10T22:46:53] SERVER DEBUG: EnetNetworkSystem init
[2018-01-10T22:46:53] SERVER DEBUG: Server is Running
That's because c4 doesn't see our custom transition definition. Let's fix this by importing our state
module before calling run()
:
from c4.core import run
from c4.conf import config
import server.server_states
config = (
version: "0.1"
)
when isMainModule:
run()
nim c -r main.nim --loglevel=DEBUG -s
...
[2018-01-10T22:48:25] SERVER DEBUG: Version 0.1.1-19
[2018-01-10T22:48:25] SERVER DEBUG: Starting server
[2018-01-10T22:48:25] SERVER DEBUG: Server is Loading
[2018-01-10T22:48:25] SERVER DEBUG: EnetNetworkSystem init
Hello world
Nice! We just broke our server startup in favor of "Hello world" output. Now revert the destructive changes and go on.
Warning: Avoid calling switch
inside of switch
. If your state graph is cyclic (i.e. you may switch to already visited states) you may face stack overflow error.