In-game Console and Scripting

Started by
1 comment, last by deadstar 16 years, 1 month ago
I haven't implemented a scripting language or a Quake-style console in my games yet. I was thinking about it and wondering if it's worth implementing a console if you already have a script hooked in. The way I thought about it is that you could run the game windowed and have the main scripts open in a text editor. Then you could (F5) refresh the scripts and get most of the benefit of inputs to the console. The console is also used for debug printing info. You could have an overlay that lists all the important variables and their values. I was wondering if people knew of good examples of a console in a game that is tied to a scripting language like lua or python. Hopefully freely downloadable. I would like to see it, because most consoles seem to stop short of being a full language. I wonder if all the error-checking, logging, and flexibility with scripting/xml/consoles is worth it for a moderately sized game. Then I think of some counterexamples and know it's worth it to some extent.
Advertisement
Here's an example of writing a Python console in Python. It uses Python's code.InteractiveInterpreter to handle parsing the Python code and sets itself as stderr and stdout to get messages. It isn't usable by itself but should give you an idea of what can be done.

import Frostyimport GameStateimport EditControlimport codeimport sysclass ConsoleState(GameState.GameState, code.InteractiveInterpreter):  def __init__(self, game):    GameState.GameState.__init__(self, game)    code.InteractiveInterpreter.__init__(self, locals())    self.held_state = None    self.font = Frosty.Font("Console Font")    self.edit = EditControl.EditControl()    self.edit.set_font(self.font)    self.history = [] # image list for display    self.ps1 = self.font.render_font(">>>", 128, 128, 255)    self.ps2 = self.font.render_font("...", 128, 128, 255)    self.prompt = self.ps1    self.input_buffer = [] # source text not processed by interpreter    self.command_buffer = [] # command history    self.command_index = 0    self.last_line = ""    self.last_line_image = None        sys.stdout = self    sys.stderr = self  def _on_key_down(self, scan_code, unicode):    if scan_code == Frosty.SDLK_ESCAPE:      self.transition(self.held_state)    else:      self.on_key_down(scan_code, chr(unicode))  def on_key_down(self, key, unicode):    if key == Frosty.SDLK_RETURN:      self.command_index = 0      text = self.edit.get_text();      self.edit.set_text("")      if self.last_line:        self.history.append( (self.last_line_image,) )        self.last_line = ""      if text:        self.command_buffer.append(text)        self.history.append( (self.prompt,                              self.font.render_font(text, 255, 255, 255)                           ) )      else:        self.history.append( (self.prompt,) )      more = self.push(text)      if more:        self.prompt = self.ps2      else:        self.prompt = self.ps1    elif key == Frosty.SDLK_UP:      if self.command_index < len(self.command_buffer):        self.command_index += 1        self.edit.set_text(self.command_buffer[-self.command_index])    elif key == Frosty.SDLK_DOWN:      if self.command_index > 1:        self.command_index -= 1        self.edit.set_text(self.command_buffer[-self.command_index])      elif self.command_index == 1:        self.command_index = 0        self.edit.set_text("")    else:      self.edit.on_key_down(key, unicode)  def render(self, surface):    self.held_state.render(surface)    filter = surface.create_compatible_filter()    filter.clear(0, 0, 0)    filter.set_alpha(192)    filter.blit(surface, 0, 0)    x = self.ps1.width    y = self.holder.height - self.edit.height()    line = 1    self.prompt.blit(surface, 0, y)    self.edit.render(surface, x, y)    size = len(self.history)    if self.last_line:      y -= 12      self.last_line_image.blit(surface, 0, y)    while (y > 0) and (line <= size):      y -= 12      if (self.history[-line]):        if len(self.history[-line]) == 2:          self.history[-line][0].blit(surface, 0, y)          self.history[-line][1].blit(surface, x + 3, y)        else:          self.history[-line][0].blit(surface, 0, y)      line += 1  def update(self, time):    self.edit.update(time)  def reset_buffer(self):    self.input_buffer = []  def push(self, line):    self.input_buffer.append(line)    source = "\n".join(self.input_buffer)    more = self.runsource(source, "<ConsoleState>")    if not more:      self.reset_buffer()    return more  def write(self, text):    if text:      lines = text.split("\n")      lines[0] = self.last_line + lines[0]      self.last_line = lines[-1]      self.last_line_image = self.font.render_font(self.last_line, 255, 255, 255)      for line in lines[:-1]:        if line:          self.history.append( (self.font.render_font(line, 255, 255, 255), ) )          Frosty.echo(line)        else:          self.history.append(None)          Frosty.echo()
Having a scripting system, as well as a console really does push up productivity.

If you're testing things out it's nice to bring up a console and type "addmodel dropship.x" instead of closing the game, editing the code, recompiling and running again.

I didn't realise its advantages until I implemented it. Now I can't live without it.

"The right, man, in the wrong, place, can make all the dif-fer-rence in the world..." - GMan, Half-Life 2

A blog of my SEGA Megadrive development adventures: http://www.bigevilcorporation.co.uk

This topic is closed to new replies.

Advertisement