507 lines
40 KiB
HTML
507 lines
40 KiB
HTML
<!DOCTYPE html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
<title>Playscii - Documentation (Game Mode)</title>
|
|
<link rel="stylesheet" type="text/css" href="./darkgrey.css" title="dark grey"/>
|
|
<style>
|
|
.titlebar-playscii {
|
|
height: 78px;
|
|
padding: 0px 10% 0px 10%;
|
|
align: left;
|
|
background-repeat: repeat-x;
|
|
background-image: url('logo_bg_tile.png');
|
|
}
|
|
.colorbar {
|
|
height: 12px;
|
|
}
|
|
.item {
|
|
padding: 0px 5%;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="titlebar-playscii">
|
|
<img src="pslogo_w_text2.png" alt="Playscii logo"/>
|
|
</div>
|
|
<div class="colorbar"></div>
|
|
|
|
<div class="main">
|
|
|
|
<br/>
|
|
|
|
<a id="intro"> <h4>Introduction</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
While I've worked hard to make game creation in Playscii as easy and inviting as possible, it's still code-based, and much of this page will assume you know the basics of Python programming. Any kind of non-code-based game creation, such as visual programming, is a far-off someday thing. If you have ideas for how it could work, I'm <a href="https://jplebreton.com/#contact_email">interested in hearing them</a>.
|
|
</p>
|
|
<p>
|
|
That said, you can make cool things happen in Game Mode with small amounts of easy to understand code, and you can "live tune" many things without having to restart Playscii to see your changes. This page will explain how.
|
|
</p>
|
|
<p>
|
|
There are some topics best covered in this format, but for other things Playscii's code itself is the best documentation. Playscii can auto-generate nicely formatted HTML documentation of its code you can view in any web browser, without an internet connection. All you have to do is select "Generate documentation" from the Help menu. It may take a moment, but within a few seconds a table of contents page should open in your web browser. (If you're running from source instead of one of the pre-compiled builds, make sure you have the <tt>pdoc</tt> Python module installed.)
|
|
</p>
|
|
<p>
|
|
If you've already generated the docs, the following link should take you to there:
|
|
<h3> <a href="./generated/pdoc_toc.html">Playscii Auto-Generated Documentation</a> </h3>
|
|
</p>
|
|
</div>
|
|
|
|
<br/>
|
|
|
|
<h3>Table of Contents</h3>
|
|
|
|
<ul>
|
|
<li> <a href="#intro">Introduction</a> </li>
|
|
<li> <a href="#gamemode">Entering Game Mode</a> </li>
|
|
<li> <a href="#menubar">Menu Bar</a> </li>
|
|
<li> <a href="#loadgame">Loading Games</a> </li>
|
|
<li> <a href="#newgame">Creating a New Project / Folder Structure of a Game</a> </li>
|
|
<li> <a href="#gameworld">Game World Overview</a> </li>
|
|
<li> <a href="#gameobject">Game Object Overview</a> </li>
|
|
<li> <a href="#gamestate">Game States</a> </li>
|
|
<li> <a href="#editing">the Editor Interface</a> </li>
|
|
<li> <a href="#propertieseditor">the Properties Editor</a> </li>
|
|
<li> <a href="#spawning">Spawning New Object Instances</a> </li>
|
|
<li> <a href="#statefacing">Object State and Facing</a> </li>
|
|
<li> <a href="#worldprops">World Properties and Globals</a> </li>
|
|
<li> <a href="#camera">Camera</a> </li>
|
|
<li> <a href="#rooms">Rooms</a> </li>
|
|
<li> <a href="#hud">HUD</a> </li>
|
|
<li> <a href="#debugview">Debug Views</a> </li>
|
|
<li> <a href="#collision">Collision Detection and Resolution</a> </li>
|
|
<li> <a href="#sound">Sound and Music Playback</a> </li>
|
|
<li> <a href="#inputhandling">Handling Input</a> </li>
|
|
<li> <a href="#console">the Developer Console</a> </li>
|
|
<li> <a href="#errorhandling">Crash/Error Handling</a> </li>
|
|
<li> <a href="#workflow">Workflow</a> </li>
|
|
<li> <a href="#examples">Tour of Example Games</a> </li>
|
|
<ul>
|
|
<li> <a href="#example_cronotest">Cronotest</a> </li>
|
|
<li> <a href="#example_maze">Maze</a> </li>
|
|
<li> <a href="#example_flood">Flood</a> </li>
|
|
<li> <a href="#example_shmup">Shmup</a> </li>
|
|
<li> <a href="#example_platso">Platso</a> </li>
|
|
</ul>
|
|
</ul>
|
|
|
|
<br/>
|
|
|
|
<a id="gamemode"> <h4>Entering Game Mode</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
By default Playscii launches into <a href="./howto_art.html">Art Mode</a>. To switch between Art Mode and Game Mode, click the top right corner of the screen, or press <code>G</code>.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="menubar"> <h4>Menu Bar</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
The menu bar along the top edge of the screen provides access to much of Playscii's functionality. Click a menu name to open it and select from the available commands. Many commands have an associated keyboard shortcut, displayed to the right of the menu entry.
|
|
</p>
|
|
<center><img src="./game_menubar.jpg" alt="the Game Mode menu bar"/></center>
|
|
</div>
|
|
|
|
<a id="loadgame"> <h4>Loading Games</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
Games in Playscii are folders that contain code and assets (art, sounds) in specific subfolders. Playscii includes some games in its application folder, but your operating system's <tt>Documents/Playscii/</tt> folder has a <tt>games/</tt> subfolder for you to put your own games in.
|
|
</p>
|
|
<p>
|
|
You can launch a game in Playscii in one of three ways:
|
|
<ul>
|
|
<li> "Open game" from the Game menu will bring up a list of available games. Load one by clicking on it. </li>
|
|
<li> Launch Playscii from the command line with <tt>-game mygame</tt> after it, where <tt>mygame</tt> is the game (ie the folder) you want to launch. </li>
|
|
<li> Include a file called <tt>autoplay_this_game</tt> in the same folder as the Playscii executable, whose first line is the name of the game you want to load. This is a simple hack for distributing games to people who don't have Playscii - you can just zip up the folder and distribute that. </li>
|
|
</ul>
|
|
</p>
|
|
</div>
|
|
|
|
<a id="newgame"> <h4>Creating a New Project / Folder Structure of a Game</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
You can start a new project with "New game" from the Game menu, and Playscii will create the appropriate folders for you:
|
|
<ul>
|
|
<li> <tt>art/</tt> - PSCI art files used by your GameObjects go here. </li>
|
|
<li> <tt>charsets/</tt> - If your game uses any <a href="./howto_art.html#customcharsets">custom character sets</a>, put them here. </li>
|
|
<li> <tt>palettes/</tt> - If your game uses any <a href="./howto_art.html#custompalettes">custom color palettes</a>, put them here. </li>
|
|
<li> <tt>scripts/</tt> - All Python scripts (<tt>.py</tt> extension) for your game go here. Playscii will create a generic "starter script" here, which may be helpful if you're new to writing Python scripts for game objects. </li>
|
|
<li> <tt>sounds/</tt> - Sound effects and music for your game, in any format supported by <a href="http://www.libsdl.org/projects/SDL_mixer/">SDL2_mixer</a>, go here. </li>
|
|
<li> <tt>start.gs</tt> - This is the default <a href="#gamestatefiles">game state save file</a> that will be loaded when you open your game. </li>
|
|
</ul>
|
|
</p>
|
|
<p>
|
|
When you create a new project, Playscii automatically creates a generic "starter script" in the <tt>scripts/</tt> folder, with a few things to help newcomers - if you already know what you're doing, you can delete or ignore it. This starter script defines two new "classes", or flavors, of game objects: a generic <a href="#gameobject">GameObject</a> subclass and a subclass of Player, which is simply a GameObject that moves around when you press the arrow keys. Before getting too deeply into these, though, a higher level overview of how all the classes fit together is in order.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="gameworld"> <h4>Game World Overview</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
In Game Mode, Playscii delegates game functionality to something called a GameWorld: loading game states, content, and scripts; managing the spawning and lifetimes of GameObjects; running much of the input->update->render loop that makes the simulation go; and handling Edit UI tasks such as selecting and dragging objects.
|
|
</p>
|
|
<p>
|
|
GameObjects are, in turn, most of what a GameWorld thinks about. A GameObject can represent the player, a non-player character, an entire screen of level geometry, a powerup, a trigger that does something when entered, an invisible marker where another object appears, and so on.
|
|
</p>
|
|
<p>
|
|
GameWorlds can also organize their objects into <a href="#gameroom">GameRooms</a>, so that only certain objects are visible and simulated at once, and can use a <a href="#gamehud">GameHUD</a> to render UI-appropriate information above everything else.
|
|
</p>
|
|
<p>
|
|
The GameWorld class also has some functions that are most appropriate to its scope, such as managing <a href="./generated/game_world.html#game_world.GameWorld.play_music">music playback</a>, <a href="./generated/game_world.html#game_world.GameWorld.get_all_objects_of_type">getting collections of objects by type</a>, and <a href="./generated/game_world.html#game_world.GameWorld.get_colliders_at_point">global collision tests</a>. Have a look at the <a href="./generated/game_world.html">GameWorld generated documentation</a> for an idea of what it handles relative to the other moving parts.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="gameobject"> <h4>Game Object Overview</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
The <a href="./generated/game_object.html">GameObject class</a> defined in the built-in <tt>game_object.py</tt> module provides the base capabilities of all objects, and you can extend this functionality by creating variants of that base class in the scripts you place in the game's <tt>scripts/</tt> subfolder. The <tt>game_util_objects.py</tt> module includes a few generic subclasses ready-made for things like the player, projectiles, NPCs, triggers, and spawners.
|
|
</p>
|
|
<p>
|
|
Every GameObject has two components that help it render (draw to the screen) and collide, respectively: a <a href="#rendering">Renderable</a> and a <a href="#collision">Collideable</a>. Renderables in turn can draw data from one or more <a href="">Art</a>s, pieces of art you can create in <a href="./howto_art.html">Art Mode</a> or generate on-the-fly with code.
|
|
</p>
|
|
<p>
|
|
GameObjects have many properties you can change, functions you can call and/or override, and <a href="#statefacing">concepts such as state and facing</a> to let you define custom behaviors. Rather than try to detail all of them here, please have a look at the <a href="./generated/game_object.html">GameObject generated documentation</a> and <a href="./generated/game_util_objects.html">Game Object Utility Library generated documentation</a>, as well as the code for the <a href="#examplegames">example games</a>, for an idea of how these can be used.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="gamestate"> <h4>Game States</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
Playscii can save the state of all objects in a GameWorld into a "save state file" not unlike a conventional savegame or <a href="https://en.wikipedia.org/wiki/Saved_game#Save_states">save state</a> in a game console emulator. The <tt>start.gs</tt> created with a new project defines the game's starting state, which you can modify how you like by adding and manipulating the objects therein. You can create additional save state files for testing purposes, or even to load in new segments of gameplay if there is no state carried over from previous segments.
|
|
</p>
|
|
<p>
|
|
Save a game's current state into a new state file with "Save new state..." from the State menu. "Save state" or <code>Control-S</code> saves over the last saved state - take care not to accidentally overwrite your <tt>start.gs</tt> with this - and "Load state" opens a list of save state files to choose from. <code>F2</code> quickly reloads the last loaded state file, which is handy for <a href="#workflow">rapid iteration</a>.
|
|
</p>
|
|
<p>
|
|
Much as <a href="./howto_art.html">Art</a> (<tt>.PSCI</tt> files) are just tiles, layers, and frames serialized into <a href="https://en.wikipedia.org/wiki/JSON">JSON</a>, save state files are all the GameObjects and GameRooms serialized into JSON, so it's possible to view and even manually edit them, though there's no non-bug reason you'd need to do this.
|
|
</p>
|
|
<p>
|
|
It's worth noting that all GameObjects with the <tt>should_save</tt> property set <tt>False</tt> do not get saved into state save files. This is appropriate for certain kinds of objects, such as projectiles or spawned enemies whose lifetimes are managed in some other way, and/or for whom saving and loading would introduce unwanted complexity into how they work.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="editing"> <h4>the Editor Interface</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
In Game Mode, the boundary between editing a game and playing it is intentionally fairly thin. The editor interface lets you create, manage, and modify GameObjects, and when this interface is disabled you're left with just the game itself, ie what players experience.
|
|
</p>
|
|
<p>
|
|
You can hide this UI at any time with "Hide edit UI" in the Game menu or <code>Shift-E</code>. When distributing games, you can add the line <tt>Application.can_edit = False</tt> to <a href="./howto_main.html#config"><tt>playscii.cfg</tt></a> which will prevent players from using the interface, switching into Art Mode, or doing anything besides just what you as a the game's designer intend.
|
|
</p>
|
|
<p>
|
|
The most basic actions one can do in this edit UI are <code>primary mouse button</code> on an object to select it and drag to move it. Selected objects have a thin pulsing outline that shows their "bounds", ie the edges of their current Art. Similar to Art Mode's <a href="./howto_art.html#tools">Select tool</a>, you can select multiple objcets at once: <code>Shift + primary mouse button</code> adds an object to the current selection set, while <code> Control + primary mouse button</code> removes an object from the selection set.
|
|
</p>
|
|
<p>
|
|
You can also select from a list of all objects in the world with "Select objects" in the Object menu, or <code>Control-L</code>. The aforementioned modifier keys have the same effect here as when you click in the world.
|
|
</p>
|
|
<p>
|
|
Selected objects will drag-move together as expected. You can also delete all selected objects with "Delete selected objects" in the Object menu.
|
|
</p>
|
|
</div>
|
|
|
|
|
|
<a id="propertieseditor"> <h4>the Properties Editor</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
The pane that appears on the right side of the screen when you have object(s) selected is the Properties Editor. It shows the properties on the selected object(s) that you're allowed to edit on a per-instance basis. Clicking on a property will bring up a window where you can enter its new value. If multiple selected objects have different values, this will be displayed in the property field instead of any single value.
|
|
</p>
|
|
<center> <img src="./game_propseditor.jpg" alt="properties editor" width="50%"/> </center>
|
|
<p>
|
|
The properties you edit in this UI are the very same properties that are declared in the object's class definition. If you want to change a value for all instances of this class, edit the code; if you want to change the value for a single instance, use the Properties Editor.
|
|
</p>
|
|
<p>
|
|
Each class defines which of its properties can be edited, and which will be saved to <a href="#gamestate">state files</a>. The <tt>serialized</tt> property is a list of string names of properties that will be saved <i>and</i> can be edited, while the <tt>editable</tt> property is a list of string names of properties that should be editable but <i>not</i> serialized - mainly useful for live tuning.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="spawning"> <h4>Spawning New Object Instances</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
You can create new instances of the classes you've defined in scripts with "Spawn object" in the Object menu, or <code>Control-P</code>. This will bring up a list of available classes. Click one to select it, and then click in the main view to spawn an object at that location.
|
|
</p>
|
|
<p>
|
|
You can also create new objects from existing ones with "Duplicate selected objects" in the Object menu.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="statefacing"> <h4>Object State and Facing</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
In almost any game that uses 2D artwork, different sprites and animations are used to represent an object's different states (standing, walking, jumping) and facings (left, front, back). GameObjects use a string variable called <tt>state</tt> and an integer called <tt>facing</tt> (with <tt>GOF_LEFT</tt>, <tt>GOF_RIGHT</tt>, <tt>GOF_FRONT</tt>, <tt>GOF_BACK</tt> as the possible values) for these.
|
|
</p>
|
|
<h4>Object State</h4>
|
|
<p>
|
|
<tt>GameObject.state</tt> can be any string, but the default state name is "stand". There is no setter method for state, you can just set it, get it, and compare it directly.
|
|
</p>
|
|
<p>
|
|
Any custom state names you define for a class need to be included in its <tt>valid_states</tt> list. The <tt>get_art_for_state</tt> <tt>get_art_for_state</tt> methods use this list to load and find state-appropriate art, respectively. When a GameObject's <tt>state_changes_art</tt> property is set True, its <tt>art_src</tt> value becomes a prefix for finding art filenames for the appropriate state, eg "player_stand", "monster_die" etc. When <tt>state_changes_art</tt> is False, the base <tt>art_src</tt> property is used and the object won't change its art unless you have custom code doing so.
|
|
</p>
|
|
<h4>Object Facing</h4>
|
|
<p>
|
|
Similar to the above, when an object's <tt>facing_changes_art</tt> property is True then <tt>get_art_for_state</tt> will try to return an art that is appropriate to both the object's current state <i>and</i> its art. The <a href="#example_cronotest">cronotest example game</a> shows this in action: "crono_stand_front" is the art for the player standing (ie not moving) and facing to the front, while "crono_walk_right" is the art for the player walking to the right.
|
|
</p>
|
|
<p>
|
|
Unique left and right facings will be used if found, otherwise <tt>get_art_for_state</tt> will mirror the one it finds so you don't need to make extra art. In general, the system will fall back to whatever facing <i>is</i> available, so that if you're just roughing in an object quickly you can get away with just having a front or side-facing art and do the other assets later.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="worldprops"> <h4>World Properties and Globals</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
GameWorld is a core Playscii class, and it is a <a href="https://en.wikipedia.org/wiki/Singleton_pattern">singleton</a> not a GameObject: it doesn't serialize itself, nor is it an appropriate place for game-specific logic. In addition to classes like Player and Projectile, the <tt>game_util_objects.py</tt> module has two special classes that enable you to manage the world's state as objects: <a href="./generated/game_util_objects.html#game_util_objects.WorldPropertiesObject">WorldPropertiesObject</a> and <a href="./generated/game_util_objects.html#game_util_objects.WorldGlobalsObject">WorldGlobalsObject</a>.
|
|
</p>
|
|
<p>
|
|
Every GameWorld has a WorldPropertiesObject, but it's given a few status flags that ensure you can't select it, delete it, or spawn a second instance. Its purpose is to mirror the properties of the world itself, and then propagate those back to the world when loading a state, so that you can edit the world's properties much as you would any other object, with "Edit world properties" in the World menu. Here you can change things like the gravity applied to physical objects, whether or not the camera auto-locks on to the player, and various debug settings.
|
|
</p>
|
|
<p>
|
|
Every GameWorld also has a WorldGlobalsObject, which is typically a custom class defined for each game, where you can put logic that manages global state. When a save state file loads, it's spawned immediately after any saved objects, so whatever it's looking for is guaranteed to be there. Other game objects can always refer to it via <tt>self.world.globals</tt>.
|
|
</p>
|
|
<p>
|
|
By default this object isn't serialized, to keep a clear separation between world data and world logic, but this isn't carved in stone. The first released Playscii game, <a href="https://jp.itch.io/endless-ladder-climbing-2">Endless Ladder Climbing 2</a>, makes fairly heavy use of a WorldGlobalsObject to manage world state.
|
|
</p>
|
|
<p>
|
|
As WorldGlobalsObject is bound to be a custom class, you can let the world know which class to use by entering the class name in <tt>WorldPropertiesObject.globals_object_class_name</tt>.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="camera"> <h4>Camera</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
A game's camera is not represented by a GameObject, rather its few properties are manipulated via the <a href="#worldprops">WorldPropertiesObject</a> (so its position at any given point is serialized in a save state file) or script code.
|
|
</p>
|
|
<p>
|
|
In addition to static positioning, a camera can have a "focus object" whose movement it tracks, set via <tt>Camera.focus_object</tt>. As a convenience, when <tt>GameWorld.player_camera_lock</tt> is True, the camera will try to stay locked on to the player.
|
|
</p>
|
|
<p>
|
|
As in Art Mode, you can manually set the camera's zoom level with "Set camera zoom" in the View menu, and you can set its position directly with the <a href="#console">dev console</a>.
|
|
</p>
|
|
<p>
|
|
It's sometimes useful to use a LocationMarker object to remember a camera position. "Move selected object(s) to camera" and "Move camera to selected object" in the View menu make it easier to do this: you can position the camera how you want it and then snap an object to it, and then later snap the camera to that object to restore the position.
|
|
</p>
|
|
<p>
|
|
<a href="#rooms">Rooms</a> can also set the camera position. This is explained in detail in the next section.
|
|
</p>
|
|
</div>
|
|
|
|
|
|
<a id="rooms"> <h4>Rooms</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
Sometimes your game will have multiple "screens" worth of content, like a large world to explore. It's usually easier, for both the designer and the computer, to not be thinking about every single object in such a world at all times. To this end, Playscii allows you to organize objects into Rooms, represented by the <a href="./generated/game_room.html">GameRoom</a> class.
|
|
</p>
|
|
<p>
|
|
Rooms are strictly an opt-in concept: every object keeps track of which rooms it's in, and an object that is not in any rooms is considered to be everywhere. So by default the world updates and renders every object in it, and you don't need to think about rooms at all if you're not using them. Most of the example games don't use rooms at all, while the <a href="#example_maze">Maze game</a> uses them extensively. If you've got Playscii open as you read these docs, opening the Maze game and poking at it might help you understand some of the functionality.
|
|
</p>
|
|
<p>
|
|
The game world has a notion of what the "current room" is, and by default it's None. Once you've created a room, with "Add room" in the Room menu, you can set it as current either with "Change current room" from the Room menu, or clicking on a room's name in the list at the bottom of that same menu - a checkmark will appear next to the current room.
|
|
</p>
|
|
<h4>Managing Room Contents</h4>
|
|
<p>
|
|
Once you have a room set as current, you can set which objects are in it with "Add selected objects to this room" and "Remove selected objects from this room". "Add/remove objects from this room" will bring up a list in the left pane, in which objects in the room are highlighted and clicking adds or removes them. On the code side, if you have a handle to the room itself you can do this with <tt>GameRoom.add_object_by_name</tt> (passing the object's string name), <tt>GameRoom.add_object</tt> (passing the object by reference), <tt>GameRoom.remove_object_by_name</tt>, and so on.
|
|
</p>
|
|
<p>
|
|
Again, objects that you know will always be on-screen - it's usually a safe bet that the player is, for example - can be kept outside of all rooms.
|
|
</p>
|
|
<p>
|
|
Objects that are not in the current room (and aren't "everywhere" as described above) are not rendered or updated (unless their <tt>GameObject.update_if_outside_room</tt> property is set True), so as you're organizing objects you'll see some of them blink out of sight.
|
|
</p>
|
|
<p>
|
|
If you get confused and what's where, you can toggle a global view of all objects in all rooms with "Show all rooms" in the Room menu.
|
|
</p>
|
|
<h4>Room Camera Markers and Edge Warps</h4>
|
|
<p>
|
|
Managing room transitions and camera changes as the player moves around are very common uses of rooms - again, the Maze game's explorable world does this a lot. If a room's <tt>camera_marker_name</tt> is set, the camera will warp there when the room is entered. You can easily set this with "Set this room's camera marker" in the Room menu.
|
|
</p>
|
|
<p>
|
|
Often you'll want to warp the player to a different room when they reach the "edge" of it. You need to provide two bits of information to the room to make this work: give it an object to define the "edges" of the room, with "Set this room's edge object" in the Room menu, and telling the world which room or point to warp to when the player reaches the north/south/east/west edges of those bounds, with "Set this room's edge warps". In the pane on the left, you'll notice you can select both room names and object names. If a room name is given for an edge warp, the player will warp to that room; if an object name is given, the player will warp to that object's location and change to that object's room.
|
|
</p>
|
|
<p>
|
|
Technically this "edge warp" functionality is doing something very similar to the <a href="./generated/game_util_objects.html#game_util_objects.WarpTrigger">WarpTrigger</a> class, but its close integration with the room concept makes it somewhat easier.
|
|
</p>
|
|
<p>
|
|
A few more intricacies of Rooms are covered in the <a href="#example_maze">Maze game</a> section below, so have a look if any of this isn't clear.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="hud"> <h4>HUD</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
The GameHUD (HUD is short for Heads-Up Display) is a simple singleton member of GameWorld that draws in 2D screen space and manages its Renderables directly, useful for UI/HUD like purposes. The whole point of a GameHUD is that it has game-specific logic, so you'll want to create a subclass in your scripts and set your <tt>GameWorld.hud_class_name</tt> to its name via the <a href="#worldprops">WorldPropertiesObject</a>, much as you set <tt>globals_object_class_name</tt>.
|
|
</p>
|
|
<p>
|
|
When you're setting Renderable sizes and locations in a GameHUD, remember that it uses OpenGL screen space coordinates, where (0,0) is the center of the screen and (1,1) is the top right corner.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="debugview"> <h4>Debug Views</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
Game Mode has a few visualization modes that are helpful for debugging in the View menu:
|
|
<ul>
|
|
<li> Show all object origins: show each object's "origin" or XYZ location using a familiar red/green/blue axis marker. </li>
|
|
<li> Show all object bounds: show each object's "bounds", ie the edges of their current Art. </li>
|
|
<li> Show all object collision: show each object's collision shapes - see the <a href="#collision">Collision</a> section below. </li>
|
|
</ul>
|
|
</p>
|
|
<p>
|
|
If you want to use debug lines for a more specific purpose, check out the <tt>renderable_line.DebugLineRenderable</tt> class - you can feed it a list of 3D coordinate tuples and it will draw those lines in the world.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="collision"> <h4>Collision Detection and Resolution</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
Each GameObject has a <a href="./generated/collision.html#collision.Collideable">Collideable</a> component that allows it to participate in collisions with other objects. A Collideable in turn can have zero or more <a href="./generated/collision.html#collision.CollisionShape">CollisionShapes</a>, of which there are two types: Circle and Box (or "AABB" for <a href="https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box">Axis-Aligned Bounding Box</a>, meaning an unrotated rectangle). Mostly commonly an object's Collideable will either have a single shape, as in the case of dynamic objects like the Player and Pickup classes, or multiple shapes arranged to approximate the shapes of tiles in an object's Art's "collision layer" - see more on this below.
|
|
</p>
|
|
<center>
|
|
<img src="./game_collision.jpg" alt="cronotest example game showing circle, box, and tile-based collision" width="50%"/>
|
|
<p>
|
|
Cronotest example game showing circle, box, and tile-based collision.
|
|
</p>
|
|
</center>
|
|
<p>
|
|
These basic collision types are represented in the <tt>CST_*</tt> enumerated values in the <a href="./generated/collision.html">collision module</a>: <tt>CST_NONE</tt>, <tt>CST_CIRCLE</tt>, <tt>CST_AABB</tt>, and <tt>CST_TILE</tt>. Set a GameObject's <tt>collision_shape_type</tt> property to one of these to get each behavior.
|
|
</p>
|
|
<p>
|
|
CST_NONE means a Collideable will not bother generating any shapes and effectively do nothing, CST_CIRCLE and CST_AABB refer to the circle and box shapes described above, and CST_TILE refers to the generation of multiple box shapes alluded to above. An object's Circle collision size can be tuned with the <tt>col_radius</tt> property, and an object's AABB collision size can be tuned with the <tt>col_width</tt> and <tt>col_height</tt> properties. If you enable the <a href="#debugview">collision debug view</a>, you can see these changes reflected in real time.
|
|
</p>
|
|
<h4>Tile-Based Collision</h4>
|
|
<p>
|
|
CST_TILE collision leverages Playscii's tile-based nature by letting you define a layer in an object's Art to be its "collision layer" - a layer whose name is defined in the <tt>GameObject.col_layer_name</tt> property - so that you can easily author collision for complex environment objects. By default any layer in an Art named "collision" will be used for this purpose, and any non-blank (<a href="./howto_art.html#indices">character index</a> 0) tile on that layer will be treated as a solid block. In the image above, you can see the red shapes with the blue outlines that define the background object's collision layer. Static background objects are the most common use case for tile-based collision
|
|
</p>
|
|
<h4>Static vs Dynamic Collision</h4>
|
|
<p>
|
|
A GameObject's collision can be static or dynamic. If it's static, it can never move. Objects with CST_TILE collision are always static, as Playscii does not currently have a computationally efficient way to handle complex tile-based collisions for moving objects. The <tt>CT_*</tt> enum values defined in the collision module define arbitrary categories, and <tt>CTG_STATIC</tt> and <tt>CTG_DYNAMIC</tt> are lists of these values. You probably won't have to check or define these values much, just understand the static vs dynamic distinction and set it appropriately for each class.
|
|
</p>
|
|
<h4>Tunneling</h4>
|
|
<p>
|
|
Tunneling refers to bugs where moving objects can punch through other objects without the expected collision behavior. If you notice this happening for a given object class, you can set its <tt>fast_move_steps</tt> property to something greater than 0 to make its collision checks more granular - basically, each move is subdivided into <tt>fast_move_steps</tt> steps, the object stops moving if a collision is found, and the collision resolution phase that happens later will push the colliding object out of what it hit. This is enabled by default for the game_util_objects.Projectile class. Playscii's collision code is still maturing so if you see collision problems you can't easily fix, please <a href="https://heptapod.host/jp-lebreton/playscii/issues">file a bug</a>.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="sound"> <h4>Sound and Music Playback</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
Playscii's audio capabilities are not extensive but they should cover basic uses. Sound assets can be in any format supported by <a href="http://www.libsdl.org/projects/SDL_mixer/">SDL2_mixer</a>, and go in the <tt>sounds/</tt> subfolder of your game folder.
|
|
</p>
|
|
<p>
|
|
<a href="./generated/game_object.html#game_object.GameObject.play_sound">GameObject.play_sound</a> plays a sound by name, and names are mapped to asset filenames in an object's <tt>sound_filenames</tt> dictionary. Sounds can play N times or loop indefinitely, and you can stop them by name with <tt>stop_sound</tt>. You can also define looping sounds to play while an object is in a given state via <tt>looping_state_sounds</tt>
|
|
</p>
|
|
<p>
|
|
Music is controlled globally with <a href="./generated/game_world.html#game_world.GameWorld.play_music">GameWorld.play_music</a> / <tt>stop_music</tt>.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="inputhandling"> <h4>Handling Input</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
Playscii's InputLord class handles application input and takes care of basic things like player movement, but you may want to have objects (the player, most commonly) handle input directly. In these cases, set the class's <tt>handle_key_events</tt> and/or <tt>handle_mouse_events</tt> properties to True, and override GameObject's <tt>handle_key_down</tt>/<tt>handle_key_up</tt> and/or <tt>clicked</tt>/<tt>unclicked</tt> functions. InputLord passes in the key as a string and modifier key statues as booleans, so you don't need to worry about decoding from SDL2 keycodes or anything. Mouse events are passed in as button numbers and give the world coordinates of the click (X and Y axes).
|
|
</p>
|
|
</div>
|
|
|
|
<a id="console"> <h4>the Developer Console</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
Playscii has a powerful developer console that can be opened by pressing <code>~</code> aka the tilde key, whose placement may vary on non-US layouts. If it's something unreasonable on your keyboard, you can rebind it to something better in your <tt>binds.cfg</tt>.
|
|
</p>
|
|
<p>
|
|
Type <tt>help</tt> at the dev console to see a list of available commands, with a brief summary of what each does. Some of these are more germane to <a href="./howto_art.html">Art Mode</a> and/or redundant with functionality available from menus. Some commands require arguments after them; if so a usage hint will display if you type the command sans arguments.
|
|
</p>
|
|
<p>
|
|
The dev console can also run arbitrary python expressions within the application's namespace! This is very powerful and potentially dangerous, so save your work before experimenting. The console's execution mechanism sets a few values so you can easily access them:
|
|
<ul>
|
|
<li> <tt>app</tt> - the application instance itself </li>
|
|
<li> <tt>ui</tt> - the application's top-level UI instance </li>
|
|
<li> <tt>camera</tt> - the current camera (Art Mode and Game Mode each have their own camera) </li>
|
|
<li> <tt>art</tt> - the current active Art in Art Mode </li>
|
|
<li> <tt>world</tt> - the GameWorld instance </li>
|
|
<li> <tt>player</tt> - the player instance in Game Mode </li>
|
|
<li> <tt>sel</tt> - the selected object in Game Mode, if only a single object is selected </li>
|
|
<li> <tt>hud</tt> - the GameWorld's GameHUD instance </li>
|
|
</ul>
|
|
</p>
|
|
<p>
|
|
An example of how one might use this: select an object in Game Mode, open the console and type <tt>sel.art.set_tile_at(0, 0, 0, 0, 1 + 3, 4, 5)</tt>. This will set the top left tile of the selected object's current Art to a specific character and color. That particular function is a bit wordy to call, but it demonstrates that you can call actual functions, use expressions, and set values directly from the console. The better you know Playscii's internals, the more powerful this tool can be for debugging and quick hacks.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="errorhandling"> <h4>Crash/Error Handling</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
As you're iterating on game code, it's possible your code will cause Playscii to crash. Since 0.8.1, where possible Playscii will try to catch this and print the Python exception information to the console instead of crashing. If you run into a situation where this reasonably should have happened but you got a crash instead, please <a href="https://heptapod.host/jp-lebreton/playscii/issues">file a bug</a>.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="workflow"> <h4>Workflow</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
If you've been reading this document sequentially most of this will have been covered already, but I hope it's clear by now that Playscii's design places a high value on making it so you can "live edit" and see changes reflected immediately as much as possible, avoiding the productivity and morale drags of having to restart the application or game every time a small change is made.
|
|
</p>
|
|
<p>
|
|
A proficient Playscii workflow includes using <a href="#gamestate">state save files</a> to rapidly reset after code changes, exposing important class tuning values for <a href="#propertieseditor">live property editing</a>, and using the <a href="#console">dev console</a> when other UI doesn't easily expose things. Also worth mentioning is that any changes made to Arts that objects reference in Art Mode will be reflected instantly back in Game Mode, so you can iterate rapidly on art and animation assets.
|
|
</p>
|
|
<p>
|
|
I'm always looking to make this workflow better, so please <a href="https://jplebreton.com/#contact_email">send me</a> any suggestions you have.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="examples"> <h4>Tour of Example Games</h4> </a>
|
|
<div class="block">
|
|
<p>
|
|
Playscii includes some example games to demonstrate many of the features available in Game Mode, so here's a summary of the learning value of each. In addition the first released Playscii game, <a href="https://jp.itch.io/endless-ladder-climbing-2">Endless Ladder Climbing 2</a>, was created with an older version of Playscii but also does some reasonably ambitious things, so you can download that and examine the <tt>scripts/</tt> folder in its game folder if you're eager to learn more.
|
|
</p>
|
|
|
|
<a id="example_cronotest"> <h4>Cronotest</h4> </a>
|
|
<div class="block">
|
|
<center> <img src="./game_cronotest.png" alt="Cronotest game" width="25%"/> </center>
|
|
<p>
|
|
No real "game" to it, this was the first test space for Game Mode made in April-May of 2015, and shows off what was new at the time: <a href="./howto_art.html#imageconversion">bitmap image conversion</a> from a certain beloved 1990s JRPG, a Player class that uses <a href="#statefacing">facing</a> to drive front/side/back view stand and walk state animations, and basic examples of circle (player and the urn), box (the chest), and tile (room walls, bed, stair railing) collision types.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="example_maze"> <h4>Maze</h4> </a>
|
|
<div class="block">
|
|
<center> <img src="./game_maze.png" alt="Maze game" width="50%"/> </center>
|
|
<p>
|
|
This game was built to put the <a href="#rooms">Rooms</a> features through their paces. Its world consists of several large tile-based background objects, a few pickups, and some very basic NPCs spawned by ObjectSpawners - all collected into different rooms and connected by a combination of "edge warps" and conventional WarpTriggers. It was made with Playscii 0.7.1 in October 2015 and I made a <a href="https://www.youtube.com/watch?v=I1tys2PCsSQ">video detailing its features</a>.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="example_flood"> <h4>Flood</h4> </a>
|
|
<div class="block">
|
|
<center> <img src="./game_flood.png" alt="Flood game" width="25%"/> </center>
|
|
<p>
|
|
The goal of this game is to "flood" all the tiles with the same color, starting with the top left tile, before you run out of turns. Press number keys 1-5 to choose the next color to flood squares adjacent to the ones you've already flooded.
|
|
</p>
|
|
<p>
|
|
This game is a simple example of direct input handling - there's no actual Player object spawned, the Board class itself handles input. The Board also directly manipulates the tiles of its own, dynamically-generated Art - notice there are no pre-authored .PSCI files in the game's folder. Setting <tt>GameObject.generate_art</tt> to True tells the object not to worry about having an <tt>art_src</tt> set.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="example_shmup"> <h4>Shmup</h4> </a>
|
|
<div class="block">
|
|
<center> <img src="./game_shmup.png" alt="Shmup game" width="25%"/> </center>
|
|
<p>
|
|
A simple 2D shoot-em-up to show off Projectiles, ObjectSpawners, and a generated starfield. Note how the EnemySpawner class overrides <tt>get_spawn_class_name</tt> to produce a weighted random enemy population. Various objects use the <tt>lifespan</tt> property to auto-destroy themselves after a fixed time. The starfield uses <tt>Art.shift_all_frames(0, 1)</tt> to efficiently scroll downward - under the hood, this uses numpy's <tt>ndarray.roll</tt> to shift the data without rewriting every tile every update.
|
|
</p>
|
|
</div>
|
|
|
|
<a id="example_platso"> <h4>Platso</h4> </a>
|
|
<div class="block">
|
|
<center> <img src="./game_platso.png" alt="Platso game" width="50%"/> </center>
|
|
<p>
|
|
A very basic single-screen platformer featuring a world with gravity and jumping, input handling to ensure each jump requires a keypress, and monsters with some basic behavior logic. Note PlatformMonster's use of <tt>set_timer_function</tt> to run its <tt>check_wall_hits</tt> method, a potentially expensive custom collision check, every 1/5 of a second instead of every frame.
|
|
</p>
|
|
<p>
|
|
Platformers live and die by their control feel and the solidity of their collision resolution and for this game those are definitely not where I'd like them to be, so if you have any suggestions on how to improve it, please let me know!
|
|
</p>
|
|
</div>
|
|
|
|
<br/><br/>
|
|
|
|
<b><a href="./howto_main.html"><< back to main documentation page</a></b>
|
|
|
|
<br/><br/><br/>
|
|
|
|
</div>
|
|
</body>
|