'+ escapeHtml(title) + '
' + escapeHtml(summary) +'
├── 404.html ├── Docs ├── ReleaseNotes │ ├── cave-11-vs-12-importing.png │ ├── cave-11-vs-12-vs-ue55.png │ ├── cave-bible-banner.png │ ├── image-1.png │ ├── image-2.png │ ├── image-3.png │ ├── image-4.png │ ├── image-5.png │ ├── image-6.png │ ├── image.png │ ├── v097 │ │ └── index.html │ ├── v0972 │ │ └── index.html │ ├── v098 │ │ └── index.html │ ├── v099 │ │ └── index.html │ └── v120 │ │ └── index.html ├── eula │ └── index.html ├── faq │ └── index.html └── roadmap │ └── index.html ├── PythonAPI ├── Assets │ ├── audio │ │ └── index.html │ ├── materials │ │ └── index.html │ └── meshes │ │ └── index.html ├── Components │ ├── gameplay │ │ └── index.html │ ├── graphics │ │ └── index.html │ ├── interface │ │ └── index.html │ ├── physics │ │ └── index.html │ └── transform │ │ └── index.html ├── Core │ ├── component │ │ └── index.html │ ├── entity │ │ └── index.html │ └── scene │ │ └── index.html ├── EditorAPI │ ├── tooling │ │ └── index.html │ └── uiModule │ │ └── index.html ├── Events │ ├── events │ │ └── index.html │ └── keycodes │ │ └── index.html ├── cave1 │ └── index.html ├── engineMisc │ └── index.html ├── engineUtils │ └── index.html ├── introduction │ └── index.html └── math │ └── index.html ├── Showcase └── games │ └── index.html ├── css ├── base.css ├── bootstrap.min.css ├── bootstrap.min.css.map ├── brands.min.css ├── fontawesome.min.css ├── solid.min.css └── v4-font-face.min.css ├── engine.pyi ├── img ├── favicon.ico └── grid.png ├── index.html ├── js ├── base.js ├── bootstrap.bundle.min.js ├── bootstrap.bundle.min.js.map └── darkmode.js ├── search ├── lunr.js ├── main.js ├── search_index.json └── worker.js ├── sitemap.xml ├── sitemap.xml.gz └── webfonts ├── fa-brands-400.ttf ├── fa-brands-400.woff2 ├── fa-regular-400.ttf ├── fa-regular-400.woff2 ├── fa-solid-900.ttf ├── fa-solid-900.woff2 ├── fa-v4compatibility.ttf └── fa-v4compatibility.woff2 /404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |Page not found
250 |Welcome to the biggest Cave release yet!
281 |In fact, this release is so big that I've started to write its release notes a while ago (Check the Release Notes for v0.9.7.2!) and was not able to put even half of it in the document.
282 |And since this release note is pretty much what's delaying the engine from actually being released, I'll leave it as a "TODO" here for now. But feel free to join our discord server to discover the rest of the new features! :)
283 |You can play any sound you want anytime by calling the cave.playSound
function. You'll need to pass the asset name for the sound (exactly how it is written in the asset browser) and the optional parameters:
Parameter | 290 |Description | 291 |
---|---|
volume | 296 |How loud or quiet the sound will be. 0 means muted, 1 means max. | 297 |
fadeIn | 300 |In seconds, if greated than zero, it will slowly fade in the sound by the amount of time you pass. | 301 |
loop | 304 |How many times you want the sound to be played. -1 means that it will play forever, zero means that it will play once and 1 (or more) means that it will play and repeat by 1 (or the number you pass). | 305 |
This function returns an AudioTrackInstance
that allows you to later change those values, pause/resume the sound and more. Reference code:
cave.playSound(name: str, volume=1.0, fadeIn=0.0, loop=0) -> AudioTrackInstance
310 |
311 | As explained above, by calling cave.playSound
, the function will return AudioTrackInstance
. Here is everything you need to know about it:
Here is an example of the AudioTrackInstance in action. The following component only plays the sound if the scene is NOT paused:
316 |class AudioExample(cave.Component):
317 | def start(self, scene):
318 | self.handler = cave.playAudio("MyCoolSound.ogg", 1.0)
319 |
320 | def update(self):
321 | if self.handler.isPaused():
322 | self.handler.resume()
323 |
324 | def pausedUpdate(self):
325 | if self.handler.isPlaying():
326 | self.handler.pause()
327 |
328 | You can use this variable to adjust the sound volume. Keep in mind that it must be between 0 and 1.
330 |volume : float
331 |
332 | Here is the basic operations to work with the sound (pause, resume and stop):
334 |pause()
335 | resume()
336 |
337 | # If you pass a fadeout (in seconds), if will slowly fade out the audio until it stops.
338 | stop(fadeOut=0.0)
339 |
340 | You can also use the handler to check certain things and status of it:
341 |# Returns true if the track is being played or is paused. False if not:
342 | isActive() -> bool
343 |
344 | isPlaying() -> bool
345 | isPaused() -> bool
346 | isFadingIn() -> bool
347 | isFadingOut() -> bool
348 |
349 | # Returns the channel that this audio is being played at:
350 | getChannel() -> int
351 |
352 | Cave Engine supports 3D sounds, but you'll need to manually call this method every frame in order to update it:
354 |calculate3D(audioPos: Vector3, maxDistance=100.0)
355 |
In order to properly manipulate materials and colors in Cave, you'll need to acknowledge two main classes: ColorSampler
and Material
. Check them below.
Be aware that right now the engine does not support custom material shaders or pipelines, but it will provide you some basic functionalities such as changing colors and textures in realtime.
295 |The way you'll tipically get a Material class instance is by retrieving them from a MeshComponent
, do NOT try to create your own instance from scratch, it will not work. Here is an example of how it's done:
# Getting the mesh component:
299 | meshComponent = self.entity.get("Mesh")
300 |
301 | # Getting the material:
302 | material = meshComponent.getMaterial()
303 |
304 | # If you want to change the material settings for a specific Entity,
305 | # you can duplicate it and reassign to the Component:
306 | materialCopy = material.getCopy()
307 | meshComponent.setMaterial(materialCopy)
308 |
309 | # Now you'll be able to do local changes to the *materialCopy*:
310 | materialCopy.albedo.set(1,0,0,1)
311 |
312 | Here is the full documentation:
315 |You probably have noticed that when it comes to Material colors, Cave Engine allows you most of the times to choose if you want to use a Texture (an image) or a raw Color (with reg, green, blue and alpha values). To archieve that, Cave uses what we call ColorSampler
. You'll find ways to change its color below.
# Returns true if the color sampler has a texture.
319 | hasTexture() -> bool
320 |
321 | # Will return the color sampler's color
322 | getColor() -> Vector4
323 | # Will return the color sampler's texture name (if there is a texture)
324 | getTexture() -> string
325 |
326 | In order to change the ColorSampler values, you can use one of those two setters, depending if you want to set it to a color or a texture.
327 |# Use this one if you want to set it to a raw color:
328 | set(r: float, g: float, b: float, a=1.0)
329 |
330 | # Use this one if you want to set it to a texture:
331 | set(textureName: str)
332 |
333 | All the following variables returns ColorSampler
class instances. Check the documentation above for more details on how to handle them.
albedo: ColorSampler
337 | roughness: ColorSampler
338 | metallic: ColorSampler
339 | normal: ColorSampler
340 | emission: ColorSampler
341 |
342 | If you need to duplicate a material (in order to do local changes to a specific Entity, for example), this is what you're looking for. It will duplicate the material and add it to the game data!
344 |getCopy() -> cave.Material
345 |
As you may know, Cave Engine allows you to create custom in game UIs by using some Builtin Components. Here you'll find their available Python APIs.
281 |If the Element have a Font, you can type texts in it do be displayed in your game. For this, use the text
variable:
text : str
285 |
286 | In order to position and scale your UI element, you can use those variables:
287 |position : cave.UIVector
288 | scale : cave.UIVector
289 |
290 | _ Note: Read the cave.UIVector
's Documentation in the Math section of this API._
You can change the component's color by adjusting one of those variables:
292 |quadAlpha : float
293 | quadColor : cave.Vector
294 |
295 | To see if the user is hovering your UI element, you can use thid method:
297 |isHovered() -> bool
298 |
In the Cave Engine you can write your own tools using python. They can be as simple as just executing a text to do something for you or more complex and well made tools, with a proper interface that contains buttons, sliders and whatever you want in a Tab format, that you can dock to the Editor's ui.
285 |To start writting your tools, create a new Python Script in the asset browser, open it in the text editor (double click) and delete the current startup code.
286 |Let's start with a simple tool that just sets some Sun light settings:
288 |import cave
289 |
290 | # You can write the code you want to using the cave API:
291 | scene = cave.getCurrentScene()
292 | sun = scene.getSun()
293 |
294 | sun.hour = 6.15
295 | sun.intensity = 5.0
296 | sun.color = cave.Vector3(1.0, 0.8, 0.8)
297 |
298 | In order to run this code, simply go to "Editor Tools.." and then "Run Script":
299 |It will run and change the sun settings, as expected. Note that you can use print(...)
here to debug your stuff as well.
Have fun!
302 |Now that you already know how to run simple scripts like that, it's time to understand how to do some a bit more advanced ones. So let's start talking about a tool with its own Tab docked in the editor, with a proper interface with buttons, sliders and so on. Here is a screenshot showing that in action. The code in the left generated the tab highlighted in red:
304 |Here is a simple code that produces a similar result and can be used as a starting point for your own tools:
306 |import cave
307 | import caveui as ui
308 |
309 | class Example(ui.DebugTab):
310 | def __init__(self):
311 | super().__init__()
312 | self.counter = 0
313 |
314 | def draw(self):
315 | ui.text("Hello, world!")
316 | ui.separator()
317 | self.counter = ui.prop("Counter", self.counter)
318 | if ui.button("Increase counter (+1)"):
319 | self.counter += 1
320 |
321 | In order to run that and register the Tab to the ui, go to the "Editor Tools..." option and open the "Register Tab..." sub menu. It will automatically identify all the classes in the code that inherits from the base class caveui.DebugTab
and show there. Simply click in the one you want to in order to register it.
In order to handle events in Cave (to see if the user pressed a keyboard or mouse key), you'll need to use the cave.Events
class. Note that you cannot instantiace your own Events class, instead of this, you can get the engine's events class instance by calling this function:
events = cave.getEvents()
282 |
283 | Here is an example of the cave Events system in action:
285 |events = cave.getEvents()
286 |
287 | if events.pressed(cave.event.KEY_W):
288 | print("The user pressed the key W!")
289 |
290 | # You can also use the event names (as strings):
291 | if events.pressed("S"):
292 | print("The user pressed the key S!")
293 |
294 | # Mouse events works the same way:
295 | if events.released(cave.event.MOUSE_LEFT):
296 | print("The user released the left mouse button!")
297 |
298 | Note: Again, do not instantiate a new instance of this class, you should always use the one provided by the cave.getEvents()
function.
pressed(event : str) -> bool
301 | pressed(event : cave.event) -> bool
302 |
303 | Returns True if the user just pressed the event key or False if not.
304 |active(event : str) -> bool
305 | active(event : cave.event) -> bool
306 |
307 | Always returns True while the user is pressing the event key or False if not.
308 |released(event : str) -> bool
309 | released(event : cave.event) -> bool
310 |
311 | Returns True if the user just released the event key or False if not.
312 |The following methods are related to mouse specific behaviours:
314 |setRelativeMouse(value: bool)
315 |
316 | When enabled, the mouse will be invisible and the mouse motion will be computed (and available at GetMouseMotion()), but not applied to the mouse cursor.
317 |If enabled, then if you want to know how much the user moved the mouse (useful to make a mouselook, for example), use the getMouseMotion
function. Reference:
getMouseMotion() -> Vector2
319 |
Please notice that this website only contains the old 0.9.x API, so if you're using the latest Cave Engine version (1.x), it may be outdated. You can still browse the old API in this website and learn from it if you want, but it's not recommended to take it for granted.
277 |The Python API for Cave 1.x now comes with the engine itself, including stubs for your reference and intellisense.
278 |If you have any questions, please contact Uniday Studio.
(Note: This API is up to date with Cave Engine v0.9.8 - PRO)
289 |Welcome to the official Cave Engine API, written to give you a good overview of how python scripting works in Cave and also serve as a Reference for you to write your own custom code.
291 |It was designed with readability in mind, so it's strongly recommended that you to take some time and read thought the entire API to better understand everything. It is full of code samples, tips and insights on how you can use those functions, classes and variables to get the best out of the engine. It also shares some cool and good to know secrets on how Cave works internally, so you can better use and understand it.
292 |Keep in mind that this API is not a Python (language) tutorial and we expect that you have a basic understanding on how this programming language works and how to write code using it, as well as some Object Oriented Programming (OOP) knowledge. If you don't know Python, check this to get started.
293 |You can start reading it in order, as it appears in the Python API top menu, starting with the Core elements (Scene, Entity, Component) and then exploring all the others. Remember that you can always go back later and use this API as a reference while writting your own codes.
295 |No! Cave Engine comes with python embedded by default and all the cave
modules and submodules are within the engine. Meaning that there is no need to install python by hand on your machine and you also don't need to install any cave
modules yourself. In fact, it's not even possible to install it yourself since they only work inside Cave Engine.
If you have any questions, suggestions or comments, feel free to Join one of our discord servers and ask there:
299 |(Use the top menu to navigate the categories)
287 |Cave Engine is a Simple, easy to use, 3D desktop Game Engine that is scriptable in Python. You can use it to make any type of games you want and release them commercially.
288 |You can expect a seamless development process with cave, with pretty much ZERO loading time: it does not require any Shader or Code compilation and asset handling is as fast as possible!
289 |In this website you'll find some useful information such as the future roadmap and license.
290 |You can Purchase Cave Engine on itch by clicking here:
292 | 293 | 294 |Alternatively, you can get access to it by becoming a Solid Patreon.
295 |If you want to talk about the engine, leave some suggestions, comments or get some help from the community, feel free to join us:
298 |We have a Portuguese and an English discord so you can feel free to join the one you're more confortable with.
' + escapeHtml(summary) +'
' + noResultsText + '
'); 52 | } 53 | } 54 | 55 | function doSearch () { 56 | var query = document.getElementById('mkdocs-search-query').value; 57 | if (query.length > min_search_length) { 58 | if (!window.Worker) { 59 | displayResults(search(query)); 60 | } else { 61 | searchWorker.postMessage({query: query}); 62 | } 63 | } else { 64 | // Clear results for short queries 65 | displayResults([]); 66 | } 67 | } 68 | 69 | function initSearch () { 70 | var search_input = document.getElementById('mkdocs-search-query'); 71 | if (search_input) { 72 | search_input.addEventListener("keyup", doSearch); 73 | } 74 | var term = getSearchTermFromLocation(); 75 | if (term) { 76 | search_input.value = term; 77 | doSearch(); 78 | } 79 | } 80 | 81 | function onWorkerMessage (e) { 82 | if (e.data.allowSearch) { 83 | initSearch(); 84 | } else if (e.data.results) { 85 | var results = e.data.results; 86 | displayResults(results); 87 | } else if (e.data.config) { 88 | min_search_length = e.data.config.min_search_length-1; 89 | } 90 | } 91 | 92 | if (!window.Worker) { 93 | console.log('Web Worker API not supported'); 94 | // load index in main thread 95 | $.getScript(joinUrl(base_url, "search/worker.js")).done(function () { 96 | console.log('Loaded worker'); 97 | init(); 98 | window.postMessage = function (msg) { 99 | onWorkerMessage({data: msg}); 100 | }; 101 | }).fail(function (jqxhr, settings, exception) { 102 | console.error('Could not load worker.js'); 103 | }); 104 | } else { 105 | // Wrap search in a web worker 106 | var searchWorker = new Worker(joinUrl(base_url, "search/worker.js")); 107 | searchWorker.postMessage({init: true}); 108 | searchWorker.onmessage = onWorkerMessage; 109 | } 110 | -------------------------------------------------------------------------------- /search/worker.js: -------------------------------------------------------------------------------- 1 | var base_path = 'function' === typeof importScripts ? '.' : '/search/'; 2 | var allowSearch = false; 3 | var index; 4 | var documents = {}; 5 | var lang = ['en']; 6 | var data; 7 | 8 | function getScript(script, callback) { 9 | console.log('Loading script: ' + script); 10 | $.getScript(base_path + script).done(function () { 11 | callback(); 12 | }).fail(function (jqxhr, settings, exception) { 13 | console.log('Error: ' + exception); 14 | }); 15 | } 16 | 17 | function getScriptsInOrder(scripts, callback) { 18 | if (scripts.length === 0) { 19 | callback(); 20 | return; 21 | } 22 | getScript(scripts[0], function() { 23 | getScriptsInOrder(scripts.slice(1), callback); 24 | }); 25 | } 26 | 27 | function loadScripts(urls, callback) { 28 | if( 'function' === typeof importScripts ) { 29 | importScripts.apply(null, urls); 30 | callback(); 31 | } else { 32 | getScriptsInOrder(urls, callback); 33 | } 34 | } 35 | 36 | function onJSONLoaded () { 37 | data = JSON.parse(this.responseText); 38 | var scriptsToLoad = ['lunr.js']; 39 | if (data.config && data.config.lang && data.config.lang.length) { 40 | lang = data.config.lang; 41 | } 42 | if (lang.length > 1 || lang[0] !== "en") { 43 | scriptsToLoad.push('lunr.stemmer.support.js'); 44 | if (lang.length > 1) { 45 | scriptsToLoad.push('lunr.multi.js'); 46 | } 47 | if (lang.includes("ja") || lang.includes("jp")) { 48 | scriptsToLoad.push('tinyseg.js'); 49 | } 50 | for (var i=0; i < lang.length; i++) { 51 | if (lang[i] != 'en') { 52 | scriptsToLoad.push(['lunr', lang[i], 'js'].join('.')); 53 | } 54 | } 55 | } 56 | loadScripts(scriptsToLoad, onScriptsLoaded); 57 | } 58 | 59 | function onScriptsLoaded () { 60 | console.log('All search scripts loaded, building Lunr index...'); 61 | if (data.config && data.config.separator && data.config.separator.length) { 62 | lunr.tokenizer.separator = new RegExp(data.config.separator); 63 | } 64 | 65 | if (data.index) { 66 | index = lunr.Index.load(data.index); 67 | data.docs.forEach(function (doc) { 68 | documents[doc.location] = doc; 69 | }); 70 | console.log('Lunr pre-built index loaded, search ready'); 71 | } else { 72 | index = lunr(function () { 73 | if (lang.length === 1 && lang[0] !== "en" && lunr[lang[0]]) { 74 | this.use(lunr[lang[0]]); 75 | } else if (lang.length > 1) { 76 | this.use(lunr.multiLanguage.apply(null, lang)); // spread operator not supported in all browsers: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Browser_compatibility 77 | } 78 | this.field('title'); 79 | this.field('text'); 80 | this.ref('location'); 81 | 82 | for (var i=0; i < data.docs.length; i++) { 83 | var doc = data.docs[i]; 84 | this.add(doc); 85 | documents[doc.location] = doc; 86 | } 87 | }); 88 | console.log('Lunr index built, search ready'); 89 | } 90 | allowSearch = true; 91 | postMessage({config: data.config}); 92 | postMessage({allowSearch: allowSearch}); 93 | } 94 | 95 | function init () { 96 | var oReq = new XMLHttpRequest(); 97 | oReq.addEventListener("load", onJSONLoaded); 98 | var index_path = base_path + '/search_index.json'; 99 | if( 'function' === typeof importScripts ){ 100 | index_path = 'search_index.json'; 101 | } 102 | oReq.open("GET", index_path); 103 | oReq.send(); 104 | } 105 | 106 | function search (query) { 107 | if (!allowSearch) { 108 | console.error('Assets for search still loading'); 109 | return; 110 | } 111 | 112 | var resultDocuments = []; 113 | var results = index.search(query); 114 | for (var i=0; i < results.length; i++){ 115 | var result = results[i]; 116 | doc = documents[result.ref]; 117 | doc.summary = doc.text.substring(0, 200); 118 | resultDocuments.push(doc); 119 | } 120 | return resultDocuments; 121 | } 122 | 123 | if( 'function' === typeof importScripts ) { 124 | onmessage = function (e) { 125 | if (e.data.init) { 126 | init(); 127 | } else if (e.data.query) { 128 | postMessage({ results: search(e.data.query) }); 129 | } else { 130 | console.error("Worker - Unrecognized message: " + e); 131 | } 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 |