• Content count

  • Joined

  • Last visited

About timetocode

  • Rank
    Advanced Member

Contact Methods

  • Website URL
  • Twitter

Profile Information

  • Gender
    Not Telling
  • Location
    Seattle, WA
  • Interests
    game development, pixel art, procedural content generation, game music

Recent Profile Visitors

800 profile views
  1. timetocode

    Streaming models for large environment

    An environment can be broken into tiles or chunks. I've only done this in two dimensions, but given that a realistic map exists almost entirely along the horizontal plane I think the same logic from a 2D game can apply. In 2D games it is common to have tiles, and then we can convert the player's x,y coordinates (x,z if in 3d) to find the tile coordinates. The tiles are just in a 1D or 2D array. Usually its a 1D array because javascript arrays are 1D naturally and some simple math can turn x,y into an index within the array. Some more math can then load the tiles that are +/- 100 units away from the player (or whatever, depending on the view). This approach can work for fairly large maps, but at some point the maps are so big that the idea of having the whole map in a single array in memory won't work. When these maps get truly massive, we can no longer just have them in memory as an array... instead we need something conceptually similar to pagination, where we only work with a finite section of data that comes from something much larger or even infinite. In games this is sometimes called chunking. Minecraft popularized this term. A very large tile map (just an example) can be divided up into chunks that are 32x32 tiles. Converting the player's coordinates to chunk coordinates is just a matter of dividing their x,y by the size of a chunk (e.g. 32 tiles x 16 pixels, or 0.25 km). After we have the chunk coordinates we can load or generate the chunk the player is in, as well as any neighboring chunks up until we feel we have enough map to satisfy the view distance. In 3D this is going to be a similar process, but perhaps there are some cooler things at our disposal such as LoD. I don't know enough about babylon to guess at chunk sizes or what the general constraints would be.
  2. timetocode

    Keep child at original scale

    I'm not sure if this applies for what you're doing, but I've done player.scale.set(3, 3) let item = new PIXI.Sprite.fromFrame('sword.png') player.addChild(item) player.itemInHand = item player.itemInHand.scale.set(1/3, 1/3) This example is pretending that I've scaled the player artwork up 3x, and then added something called `itemInHand` to the player which I didn't want scaled up, perhaps because I had already drawn the object large enough. So the object is scaled up, but one its children is scaled down, just to undo the scaling. The general pattern is parent.scale.x = n; child.scale.x = 1/n; not that I've ever applied such a thing generally, only ever explicitly in corner cases. I'm also using a centered anchor (obj.anchor.set(0.5, 0.5)).
  3. To bundle sprites I use the free version of texture packer. The primary feature for me there is that I can just put all of the sprites, as individual frames, into a folder and point texture packer to it. Texture packer can then automagically produce two files: a spritesheet.png where all the images exist together, and a spritesheet.json that basically describes the locations (x,y,width,height) of each image. Pixi can load this and then exposes them via the function `fromFrame('/goblin_north_attack0.png')` etc. So PIXI.Sprite.fromFrame('filename') and PIXI.Texture.fromFrame('filename') are two ways to use it. On a technical level these are no longer the files that they're named after. These are just rectangular selections from the generated spritesheet. The folders become part of the sprite names, so one way to organize instead of goblin_north_attack0, is to make /goblin/north/attack/0.png and then that whole string ` /goblin/north/attack/0.png` is the name of the frame. I'm not super keen on naming something 0.png, but this can be nice if the source art work has a lot of sprites... and really it can be named anything, all we truly need from it is that it has a number somewhere that we plan on incrementing for moving through the animation. Multiple spritesheets are still an option though, even doing the above. One just has to be sure not to name a sprite the same thing in both sheets (easily done via the folder trick, as it ends up being a prefix to everything in it). I usually end up with a spritesheet for all the characters, items, creatures etc, and then another spritesheet for the game world if I'm using TiledMapEditor which has its own format. I've written a barebones exporter from TiledMapEditor to pixi if anyone wants it. I'd say that this solves many problems, and it does, but it isn't without some tedious work b/c usually whatever drawing program I use or whatever art I purchase has its own ideas about the frame format. For example most purchased sprites are sold as images containing frames in rows or grids. Most of the drawing programs I've used export animations as a row of sprites all right next to each other, or as a folder of individual frames that have been named automatically. They need to be cut up into individual frames and renamed to be put into a single sheet. If the volume of the work for cutting the images out and naming them is going to exceed an hour, I usually go fire up some imagemagick tutorials. Imagemagick is a command line script that can do things like split a grid-aligned spritesheet into multiple images. I end up doing this pretty much any time I purchase artwork... usually after a fair amount of opening the spritesheet in drawing programs and displaying grids to figure out the frame width and height, and sometimes some manual removal of areas that I don't plan on feeding into the scripts. I'd also like to note that the code I pasted above is very pseudo. Just skimming it again I can see that the checks for varying things are off by 1. There's also usually some section about facing left or facing right, and then taking the set of animations (e.g. goblin_right_attack) and flipping them by setting the sprite's scale.x to be -1 (only applies if left/right are just going to be mirrors of each other). Good luck!
  4. I'm not sure if this is improper, but I've always just skipped the whole animatedSprite thing and just made animations manually. I also just put every single sprite together in one spritesheet and name them stuff like goblin_left_run0, globin_up_attack6, etc. I put the whole game in a requestAnimationFrame loop, and invoke update on every entity, and then its up to that entity's own animation code to see if its graphics should change. Here's some pseudo code for the core loop that changes the frames, as well as idle, run, and attack. This would hypothetically be inside of a class, but I've written it just plain. // defaulting to the 4 frame idle animation let animationName = 'idle' let frameNumber = 0 let acc = 0 // a variable that stores time let maxFrame = 4 let frameDelay = 0.5 update(delta) { // accumulate time acc += delta // is it time for next frame? if (acc > frameDelay) { // next frame frameNumber++ if (frameNumber > maxFrame) { // loop to start of the animation frameNumber = 0 } // change the graphics sprite.texture = PIXI.Texture.fromFrame(animationName + '_' + frameNumber + '.png') } } // change to the run animation run() { animationName = 'run' acc = 0 frameNumber = 0 maxFrame = 8 frameDelay = 0.250 } // change to the attack animation attack() { animationName = 'attack' acc = 0 frameNumber = 0 maxFrame = 12 frameDelay = 0.180 } //etc Maybe in the end its not too different than the animatedSprite... except for very explicit control over the timings/loops, and no specific arrays of frames (though they're implied by the names of the frames in the spritesheet). Performance will be fine as in the end all any of this does is display a small subsection of a texture that is already loaded -- one can easily have hundreds on a low end machine, and sometimes many thousands.
  5. What's the right way to load multiple models from blender (or anything), and then spawn them many times into the game world? Let's say I have a tree.obj and a house.obj... and then I want to load it, without rendering anything yet, and then I'm going to programmatically generate terrain and add 200 trees and 20 houses. I've tried LoadAssetContainer, but I couldn't seem to invoke scene.createMesh or createMaterial on the data... all I could get to work was container.addAllToScene(). Also is there a friendly name for the loaded model? I was able to position it by doing container.meshes[0].position.y = 30, but I assume there's another way to interact with it. So the questions are: which file format for the blender objects (obj, babylon.. then one model per file..?) which loader to use how to load without spawning the object into the world how to spawn multiple of the object into the world (clone..? then position+rotate?) how to remove individual objects without unloading the source mesh Thanks for the help
  6. timetocode

    Loading models in NullEngine

    Looks like the answer is that './' and '/' are not valid paths for either babylon or xhr (not sure which). This syntax will work in the client, but not on the server. I was able to get it working with the full path: BABYLON.SceneLoader.LoadAssetContainer('http://localhost:8080/', 'cubio.obj', scene, (container) => { //etc...
  7. How does one load a *.obj in NullEngine? const BABYLON = require('babylonjs') require('babylonjs-loaders') // mutates something globally global.XMLHttpRequest = require('xhr2').XMLHttpRequest ... BABYLON.SceneLoader.LoadAssetContainer("./", "cubio.obj", scene, function (container) { console.log('CONTAINER', container) container.addAllToScene() }) Error: ...\node_modules\xhr2\lib\xhr2.js:206 throw new NetworkError("Unsupported protocol " + this._url.protocol); Do I need to do something to xhr2 to teach it about obj?
  8. I've got collisions versus a heightmap via BABYLON.GroundMesh. I've got collisions between my players and a few random walls and other primitives that can be spawned in the game via moveWithCollisions. I'm using NullEngine and everything is multiplayer, so the scaling is important. Anything that gets added to the world needs to be able to be part of collisions against a couple hundred entities each frame. The heightmap is super high performance intrinsically and has no problem. moveWithCollisions is okay too so far, but I only have a few objects in the game total... but I'm imagining that if this ever becomes too expensive I can use the octree in babylon or roll my own spatial structure. Now I'd like to begin putting structures into this world that the player can walk through. These houses are going to have doorways, windows, and staircases. I'd like the player to be able to move through the doorways and larger windows, as well as *up* the staircases. So now I have a million questions, for those who have made things like this before, or just generally know about 3D collisions. I have a fair amount of programming experience as a gamedev+network programmer, but I'm very new to 3D. So my questions are really from the perspective of "how do I do a thing in 3d?" and "how do I do this thing via babylon.js?" I've been doing blender tutorials for two weeks in preparation. Whats the general idea behind making a low-poly house (interior and exterior)... do I just model the whole thing inside and out in blender and import it? Should the house be one mesh or multiple? Is the mesh that I create in blender the same as the mesh I should use for collisions? Is this going to magically work via moveWithCollisions? What about staircases? Should they be a ramp instead of steps? What about the doors/windows/openings? Do these just work? How do I place the house down into the terrain? I've got a bunch of ideas for this, from flattening the terrain, to connecting the foundation mesh to the ground, to just giving the houses a big fat block of a foundation and sinking it into the heightmap.. but would love to hear some options.
  9. timetocode

    Math for moving rotated objects

    I *think* those are all the same for me. I'm not completely certain, but I tried logging several things out and there were either the same, close enough (floating point) or undefined. I noticed that moving the shooting logic to after the movement logic produces a perfect synchronization. However, while that does get the `forward vector` identical between server and client, it is incorrect for gameplay reasons (the shot needs to occur before moving each frame, not after). So I've tried moving 0,0,0 before shooting, and it seems to fix everything: mesh.moveWithCollisions(BABYLON.Vector3.Zero()) For some reason this fixes everything. I can just use that just fine... but is it indicating that I've done something else wrong? Here's the full code, if anyone is interested. Sorry its a bit much to read, and all the key information is probably above somewhere. move(command, tick) { // primary attack if (command.primary) { if (this.weaponSystem.canFire()) { this.weaponSystem.fire() this.mesh.moveWithCollisions(BABYLON.Vector3.Zero()) // fix let f = BABYLON.Vector3.TransformCoordinates(BABYLON.Vector3.Forward(), this.mesh.getWorldMatrix()) let d = f.subtract(this.mesh.position) let v = d.normalize() let ray = new BABYLON.Ray(this.mesh.position, v, 100) var hit = this.scene.pickWithRay(ray, (mesh) => { // don't hit yourself if (mesh === this.mesh) { return false } return true }) v = hit.pickedPoint if (v) { // abstraction of firing logic, b/c server and client do different things when they fire // client: draws a debug ray // server: registers damage against a player this.wInterface.fire(this.id, this.x, this.y, this.z, v.x, v.y, v.z) } } } this.weaponSystem.update(command.delta) let camVector = new BABYLON.Vector3( // TODO: rename these variables to camVectorX,Y,Z; they are no longer rotations command.rotationX, command.rotationY, command.rotationZ ) this.mesh.lookAt(this.mesh.position.add(camVector.negate())) let unit = BABYLON.Vector3.Zero() if (command.forward) { unit.z += 1 } if (command.backward) { unit.z -= 1 } if (command.left) { unit.x -= 1 } if (command.right) { unit.x += 1 } unit.normalize() let matrix = BABYLON.Matrix.RotationAxis(BABYLON.Axis.Y, this.mesh.rotation.y) let heading = new BABYLON.Vector3( unit.x * this.speed * command.delta, unit.y * this.speed * command.delta, unit.z * this.speed * command.delta, ) let movement = heading.clone() if (command.jump) { // jetpack movement.y += 10 * command.delta } else { // gravity-ish movement.y -= 10 * command.delta } let movementVector = BABYLON.Vector3.TransformCoordinates(movement, matrix) this.mesh.moveWithCollisions(movementVector) // collision against terrain let y = this.scene.ground.getHeightAtCoordinates(this.mesh.position.x, this.mesh.position.z) // suspicious of this... it is movement that occurs *after* moveWithCollisions // it can't be 100% of the problem tho, because this only occurs when resting on the ground // and desync issues still occur while flying far above the mesh if (this.mesh.position.y < y + 0.5) { this.mesh.position.y = y + 0.5 } } Attached is now a picture of it working. It is a little hard to see but the red & white debug-shot-tubes are overlapping perfectly now.
  10. timetocode

    Math for moving rotated objects

    I've added firing a Ray forward to the above logic and I'm running into a problem. I begin calculating the direction of the ray similarly to the forward-ish logic from above: let f = BABYLON.Vector3.TransformCoordinates(BABYLON.Vector3.Forward(), mesh.getWorldMatrix()) console.log('f', f) console.log('mesh.position', this.mesh.position) console.log('mesh.rotation', this.mesh.rotation) Problem: `f` is different on server and client, even though mesh.position and mesh.rotation are the same. So I guess mesh.getWorldMatrix() is based off more than just position and rotation..is that correct? What other data do I need to synchronize? I think some part of the transform is out of sync. Log output from server: [1] f t { [1] x: 87.71782332658768, [1] y: 28.766663193702698, [1] z: 39.23026758432388 } [1] mesh.position t { [1] x: 87.25551778257102, [1] y: 29.250780211047058, [1] z: 38.58501679257934 } [1] mesh.rotation t { x: 0.6739999993520329, y: 0.7990000362848866, z: 0 } Log output from client: f t {x: 87.81546431779861, y: 28.62666380405426, z: 39.12993723154068} mesh.position t {x: 87.25551778257102, y: 29.250780211047058, z: 38.58501679257934} mesh.rotation t {x: 0.6739999993520329, y: 0.7990000362848866, z: 0} All there is to see amongst these numbers are that mesh.position and mesh.rotation are the same on server and client, but calculating `f` via mesh.getWorldMatrix produces different results. I've attached a picture of the difference. Rays created on the clientside are rendered in white, and rays created on the serverside on rendered in red. The correct result would be that the white and red rays overlap perfectly.
  11. timetocode

    Math for moving rotated objects

    Iv'e been experimenting with a few things, mostly centered around TransformCoordinates, and Matrix.RotationAxis(axis, mesh.rotation). I've been trying this for forward on a mesh: let forward = BABYLON.Vector3.TransformCoordinates(BABYLON.Vector3.Forward(), mesh.getWorldMatrix()) let dir = forward.subtract(mesh.position).normalize() Then this for forward on a camera: let cameraRay = camera.getForwardRay().direction And then for multiplayer, when moving a player on the server, I send the cameraRay and the controls over the network and move forward/back and strafe left/right like this: let camVector = new BABYLON.Vector3( command.cameraVectorX, command.cameraVectorY, command.cameraVectorZ ) // rotates the player this.mesh.lookAt(this.mesh.position.add(camVector.negate())) let unit = BABYLON.Vector3.Zero() if (command.forward) { unit.z += 1 } if (command.backward) { unit.z -= 1 } if (command.left) { unit.x -= 1 } if (command.right) { unit.x += 1 } unit.normalize() let matrix = BABYLON.Matrix.RotationAxis(BABYLON.Axis.Y, this.mesh.rotation.y) let heading = new BABYLON.Vector3( unit.x * this.speed * command.delta, unit.y * this.speed * command.delta, unit.z * this.speed * command.delta, ) let movement = heading.clone() // no need to clone if (command.jump) { // no-accel jetpack movement.y += 10 * command.delta } else { // really fake no-accel gravity movement.y -= 10 * command.delta } let movementVector = BABYLON.Vector3.TransformCoordinates(movement, matrix) this.mesh.moveWithCollisions(movementVector) // and then for shooting, ^ `command` is the network object holding the data from the client It is getting a little better, still not sure if these are good ways to do things or not
  12. timetocode

    Math for moving rotated objects

    What does one pass to getDirection?
  13. What are the ways of moving a rotated object in the direction that it is facing (or left, or right, or backwards, up, or down)? How much does this change if the game is a first person shooter (player's head always points up to Y) versus a space ship game (where the spaceship can go upside down and even barrel roll) ? I understand that I can take a vector such as BABYLON.Vector3.Up() and locallyTranslate it via a mesh that has a rotation... but how do I describe this direction as a Vector3 (instead of immediately applying it to the mesh) so that I can use moveWithCollisions(movementVector)? How hard is it to do this math manually? Should I be studying quaternions? Thanks Here's some of my movement code (which works, ish) but is very indirect: // face player mesh to face the same direction as the camera this.mesh.lookAt( this.mesh.position.add( new BABYLON.Vector3( -command.rotationX, -command.rotationY, -command.rotationZ ) ) ) // controls: forward, backward, left, right let unit = BABYLON.Vector3.Zero() if (command.forward) { unit.z += 1 } if (command.backward) { unit.z -= 1 } if (command.left) { unit.x -= 1 } if (command.right) { unit.x += 1 } unit.normalize() // to prevent diagonal movement being faster // full vector, movement and magnitude let velocityCoef = this.speed * command.delta this.velocity.x += unit.x * velocityCoef this.velocity.y += unit.y * velocityCoef this.velocity.z += unit.z * velocityCoef // no idea how to align the velocity vector with // the direction we're facing... so locallyTranslate it let temp = this.mesh.position.clone() this.mesh.locallyTranslate(this.velocity) // but locallyTranslate doesnt do collisions.. so lets just // teleport back to where we were and calculate what vector // we just moved along let diff = this.mesh.position.subtract(temp) this.mesh.position.copyFrom(temp) // now we have the vector that we would've moved, let's // try it again with collisions this.mesh.moveWithCollisions(diff) this.velocity.x = 0 this.velocity.y = 0 this.velocity.z = 0
  14. timetocode

    Blurred text

    I don't know the real answer, but another trick can be to make the text bigger and then scale it down. It isn't perfect, but it is less blurry: https://www.babylonjs-playground.com/#2ARI2W#4 (doubled size then scaled to 0.5)
  15. After trying multiple ways of expressing the camera direction and the movement of the player, I think I've found a combination that allows for deterministic rotation+movement of the player. Summary: camera.getForwardRay().direction instead of camera.rotation player mesh.lookAt a negative version of the camera forward ray instead of mesh.rotation = camera.rotation mesh.locallyTranslate(velocity) where velocity is a representation of WASD/arrowkeys instead of mesh.getDirection This produces something that looks and feels very similar to what I had above, except that moving around while also changing direction now works without triggering any clientside prediction errors. I can't say that I know why, but I do suspect that what I had in my first post just sorta worked by accident, possibly because the network layer was reconciling the small differences in client/server state before they got big enough to create a visual defect (maybe...), and that this new implementation is more technically correct and does not need any reconciliation. Here's the newer code Clientside camera and sending input to server: let cameraRay = this.renderer.camera.getForwardRay().direction // sending data from client to server this.client.addCommand( new PlayerInput( input.w, input.a, input.s, input.d, cameraRay.x, cameraRay.y, cameraRay.z, delta // this is the deltaTime for the frame in which the input occured ) ) The movement code (used both for the clientside prediction, and the server side movement): this.mesh.lookAt( this.mesh.position.add( new BABYLON.Vector3(-command.rotationX, -command.rotationY, -command.rotationZ), 0, 0, 0 ) ) // unit vector of our movement let unit = BABYLON.Vector3.Zero() if (command.forward) { unit.z += 1 } if (command.backward) { unit.z -= 1 } if (command.left) { unit.x -= 1 } if (command.right) { unit.x += 1 } unit.normalize() // full vector, movement and magnitude let velocityCoef = this.speed * command.delta let velocity = unit.multiplyByFloats(velocityCoef, velocityCoef, velocityCoef) this.mesh.locallyTranslate(velocity) let y = this.scene.ground.getHeightAtCoordinates(this.mesh.position.x, this.mesh.position.z) // added a little padding to keep the cube off of the ground if (this.mesh.position.y < y + 1) { this.mesh.position.y = y + 1 } I'm going to be building two games out of this, one where the player moves around a bit like a spaceship (xyz), and another where the player moves around in a standard first person way (xz). For the spaceship I'm just going to add controls that allow for z rotation of camera (pitch,yaw, roll, w/e its called). For the first person shooter I'm just going to zero out the y component of the movement and make sure the unit vector purely moves along x, z with only the terrain and a jump affecting y. So if anyone has any general suggestions or tips about movement in general, I'm all ears .