Commit c78562cc authored by insert's avatar insert 🎺

Initial Commit

parents
node_modules
old
dist
\ No newline at end of file
{
"scripts": {
"dev": "webpack-dev-server --mode development --open -w --hot"
},
"devDependencies": {
"@types/react": "^16.8.19",
"@types/react-dom": "^16.8.4",
"babel-core": "^6.26.3",
"babel-loader": "^8.0.6",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"css-loader": "^2.1.1",
"file-loader": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"style-loader": "^0.23.1",
"ts-loader": "^6.0.2",
"typescript": "^3.5.1",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.5.0"
},
"dependencies": {
"react": "^16.8.6",
"react-dom": "^16.8.6",
"tumult": "^3.0.11"
}
}
import { Texture2D } from '../../registry/texture';
import player from './player.png';
export const PlayerTexture = new Texture2D(player);
\ No newline at end of file
import * as Entity from './entities/entity';
import * as Tileset from './tilesets/tileset';
import { Texture2D } from '../registry/texture';
async function LoadTextures(set: any) {
for (let key in set) {
if (key === 'default') continue;
let texture = set[key] as Texture2D;
await texture.load();
}
}
export async function LoadAllTextures() {
await LoadTextures(Entity);
await LoadTextures(Tileset);
}
\ No newline at end of file
import { Texture2D } from '../../registry/texture';
export default class Tileset extends Texture2D {
tileSize: number;
/**
* Create a new tileset
* @param src Location of tileset
* @param tilesize Size of an individual tile
*/
constructor(src: string, tileSize: number) {
super(src);
this.tileSize = tileSize;
}
getTile(tx: number, ty: number) {
return {
sx: tx * this.tileSize,
sy: ty * this.tileSize
};
}
render(context: CanvasRenderingContext2D, x: number, y: number, tx: number, ty: number, width: number = this.tileSize, height: number = this.tileSize) {
let { sx, sy } = this.getTile(tx, ty);
context.drawImage(this.image, sx, sy, this.tileSize, this.tileSize, x, y, width, height);
}
}
import image from './outside.png';
export const TilesetOutside = new Tileset(image, 16);
\ No newline at end of file
import * as React from 'react';
type CanvasProps = {
logic: (delta: number) => void,
render: (CanvasRenderingContext2D) => void
};
class Canvas extends React.Component<CanvasProps> {
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
constructor(props) {
super(props);
}
componentDidMount() {
const canvas = this.refs.canvas as HTMLCanvasElement;
const ctx = canvas.getContext("2d");
this.canvas = canvas;
this.ctx = ctx;
this.doLogic();
this.doRender();
}
doLogic() {
this.props.logic(1 / 60);
setTimeout(() => this.doLogic(), 1 / 60);
}
doRender() {
this.canvas.width = this.canvas.width;
this.ctx.fillStyle = 'white';
this.ctx.fillRect(0, 0, 32 * 16, 32 * 16);
this.props.render(this.ctx);
requestAnimationFrame(() => this.doRender());
}
render() {
return(
<canvas ref="canvas" width={32 * 32} height={32 * 64} />
)
}
}
export default Canvas;
\ No newline at end of file
import { Bound } from '../physics/bounds';
/**
* Position of an entity in grid coordinates from bottom-left
*/
export interface Position {
x: number,
y: number
}
export abstract class Entity {
bounds: Bound;
position: Position;
constructor(bounds: Bound) {
this.bounds = bounds;
this.position = {
x: 0,
y: 0
};
}
setPosition(x: number, y: number) {
this.position = {x, y};
}
abstract render(ctx: CanvasRenderingContext2D);
};
\ No newline at end of file
import { Entity } from './entity';
import { PlayerTexture } from '../assets/entities/entity';
export class Player extends Entity {
constructor() {
super({
width: 0.75,
height: 1.875
});
}
render(ctx: CanvasRenderingContext2D) {
let {x, y} = this.position;
ctx.drawImage(PlayerTexture.image, x * 32, y * 32 - 60, 24, 60);
}
}
\ No newline at end of file
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Chunk } from './world/chunk';
import { Player } from './entities/player';
import { LoadAllTextures } from './assets/loader';
import Canvas from './components/canvas';
let chunk = new Chunk();
chunk.$noise(0);
let chunk2 = new Chunk();
chunk2.$noise(16);
let player = new Player();
player.position.y = 2;
export let selected = {
tx: -1,
ty: -1
};
let motion = {
x: 0,
y: 0
};
let onFloor = false;
let gravity = 9.81;
let pressed = new Set();
const Ceil = (x: number) => x % 1 > 0.99 ? Math.ceil(x) : Math.floor(x);
class Game {
async run() {
await LoadAllTextures();
ReactDOM.render(<Canvas logic={game.logic} render={game.render} />, document.getElementById('root'));
}
logic(delta: number) {
let { x, y } = player.position;
if (pressed.has(65)) {
motion.x = pressed.has(16) ? -3 : -1.5;
}
if (pressed.has(68)) {
motion.x = pressed.has(16) ? 3 : 1.5;
}
motion.y += gravity * delta;
let targetY = y + motion.y * delta;
let checkW = new Set<number>();
// check collisions below
let width = 0;
while (width <= player.bounds.width) {
checkW.add(width);
width += Math.min(1, player.bounds.width);
}
checkW.add(player.bounds.width);
checkW.forEach(width => {
let tile = chunk.getTile(Math.floor(x + width), Math.floor(targetY));
if (tile !== 0) {
motion.y = 0;
}
tile = chunk.getTile(Math.floor(x + width), Math.floor(targetY - player.bounds.height));
if (tile !== 0) {
motion.y = 0;
}
});
let targetX = x + motion.x * delta;
let checkY = new Set<number>();
// check sideways collisions
let height = 0;
while (height <= player.bounds.height) {
checkY.add(height);
height += Math.min(1, player.bounds.height);
}
checkY.add(player.bounds.height);
checkY.forEach(height => {
let tile = chunk.getTile(Math.floor(targetX), Math.floor(y - height));
if (tile !== 0) {
motion.x = 0;
}
tile = chunk.getTile(Math.floor(targetX + player.bounds.width), Math.floor(y - height));
if (tile !== 0) {
motion.x = 0;
}
});
x += motion.x * delta;
y += motion.y * delta;
if (motion.x != 0) motion.x -= 0.1 * motion.x;
player.setPosition(x, y);
let temp = false;
checkW.forEach(width => {
let tile = chunk.getTile(Math.floor(x + width), Ceil(y));
if (tile !== 0) {
temp = true;
}
});
onFloor = temp;
}
render(ctx: CanvasRenderingContext2D) {
ctx.imageSmoothingEnabled = false;
chunk.render(ctx, 0);
chunk2.render(ctx, 16);
player.render(ctx);
ctx.fillStyle = 'red';
ctx.fillRect(selected.tx * 32, selected.ty * 32, 32, 2);
ctx.fillRect(selected.tx * 32, selected.ty * 32 + 30, 32, 2);
ctx.fillRect(selected.tx * 32, selected.ty * 32, 2, 32);
ctx.fillRect(selected.tx * 32 + 30, selected.ty * 32, 2, 32);
}
};
const game = new Game();
export default game;
document.addEventListener('mousemove', (ev) => {
selected = {
tx: Math.floor((ev.clientX - 8) / 32),
ty: Math.floor((ev.clientY - 8) / 32)
};
}, false);
document.addEventListener('keydown', (ev) => {
pressed.add(ev.keyCode);
if (ev.keyCode === 32 && onFloor) {
motion.y = -8;
}
if ([32, 65, 68].indexOf(ev.keyCode) > -1)
ev.preventDefault();
}, false);
document.addEventListener('keyup', (ev) => {
pressed.delete(ev.keyCode);
}, false);
\ No newline at end of file
declare module '*.png'
declare module "*.json" {
const value: any;
export default value;
}
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>game</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
\ No newline at end of file
import game from './game';
game.run();
\ No newline at end of file
export interface AABB {
minX: number,
minY: number,
maxX: number,
maxY: number
};
export interface Bound {
width: number,
height: number
};
\ No newline at end of file
import { Texture } from './texture';
import { Registry } from './registry';
import { TilesetOutside } from '../assets/tilesets/tileset';
interface PatternOverlay {
type: 'pattern',
pattern: number[]
}
export type OverlayRule = PatternOverlay;
export interface Block {
texture: Texture,
overlayRule?: OverlayRule,
description?: string
};
const registry: Registry<Block> = {
0: {
texture: {
type: 'custom',
render: () => {}
},
description: 'weird gaseous substance apparently containing Oxygen'
},
1: {
texture: {
type: 'tileset',
tileset: TilesetOutside,
tx: 0,
ty: 0
},
overlayRule: {
type: 'pattern',
pattern: [
2,2,2,2,
2,2,2,2,
4,4,4,4,
4,4,4,4,
2,2,2,2,
2,2,2,2,
4,4,4,4,
4,4,4,4,
]
},
description: 'tiny stone'
},
2: {
texture: {
type: 'tileset',
tileset: TilesetOutside,
tx: 0,
ty: 1
},
description: 'hard'
}
};
export default registry;
export function GetBlock(id: number) {
return registry[id];
}
\ No newline at end of file
export interface Registry<T> {
[key: number]: T
}
\ No newline at end of file
import Tileset from '../assets/tilesets/tileset';
export type Texture = TextureTileset | Texture2D | CustomRenderer;
export class Texture2D {
type: '2d';
src: string;
image: HTMLImageElement;
/**
* Create a new 2D texture
* @param src Location of texture
*/
constructor(src: string) {
this.src = src;
this.image = new Image();
}
/**
* Load the tileset for rendering
*/
load() {
return new Promise((resolve, reject) => {
this.image.onload = resolve;
this.image.onerror = reject;
this.image.src = this.src;
});
}
};
export interface TextureTileset {
type: 'tileset',
tileset: Tileset,
tx: number,
ty: number
};
interface CustomRenderer {
type: 'custom',
render: (ctx: CanvasRenderingContext2D, x: number, y: number) => void
};
\ No newline at end of file
import { Chunk, CHUNK_WIDTH, CHUNK_HEIGHT } from '../world/chunk';
import { Block, GetBlock, OverlayRule } from '../registry/blocks';
interface Blend {
direction: 'negX' | 'negY' | 'posX' | 'posY',
x: number,
y: number,
rule: OverlayRule
};
const list = ['negX', 'negY', 'posX', 'posY']
export class Blender {
ctx: CanvasRenderingContext2D;
blends: Blend[];
constructor(ctx: CanvasRenderingContext2D) {
this.ctx = ctx;
this.blends = [];
}
check(chunk: Chunk, x: number, y: number, block: Block, ox: number) {
if (!block) block = GetBlock(chunk.getTile(x, y));
let targets = [
chunk.getTile(x - 1, y),
chunk.getTile(x, y - 1),
chunk.getTile(x + 1, y),
chunk.getTile(x, y + 1)];
targets.forEach((tile, i) => {
let target = GetBlock(tile);
if (!target) return;
if (target === block) return;
if (target.overlayRule) {
this.blends.push({
direction: <any> list[i],
x: (x + ox),
y,
rule: target.overlayRule
});
}
});
}
render() {
const ctx = this.ctx;
let image = ctx.getImageData(0, 0, CHUNK_WIDTH * 32, CHUNK_HEIGHT * 32);
const X_WIDTH = CHUNK_WIDTH * 32 * 4;
for (let i=0;i<this.blends.length;i++) {
let blend = this.blends[i], sx = blend.x * 32, sy = blend.y * 32, pattern = blend.rule.pattern;
switch (blend.direction) {
case 'negX':
sx *= 4;
for (let y=0;y<32;y++) {
let o = (sy + y) * X_WIDTH + sx;
let max = pattern[y];
for (let x=0;x<max;x++) {
let d = o + (x * 4);
let f = o - ((x + 1) * 4);
image.data[d ] = image.data[f ];
image.data[d + 1] = image.data[f + 1];
image.data[d + 2] = image.data[f + 2];
}
}
break;
case 'posX':
sx *= 4;
for (let y=0;y<32;y++) {
let o = (sy + y) * X_WIDTH + sx + 32 * 4;
let max = pattern[y];
for (let x=0;x<max;x++) {
let d = o + (-x * 4);
let f = o - (-x * 4);
image.data[d ] = image.data[f ];
image.data[d + 1] = image.data[f + 1];
image.data[d + 2] = image.data[f + 2];
}
}
break;
case 'negY':
sx *= 4;
for (let x=0;x<32;x++) {
let o = sy * X_WIDTH + sx + (x * 4);
let max = pattern[x];
for (let y=0;y<max;y++) {
let d = o + (y * X_WIDTH);
let f = o - ((y + 1) * X_WIDTH);
image.data[d + 0] = image.data[f ];
image.data[d + 1] = image.data[f + 1];
image.data[d + 2] = image.data[f + 2];
}
}
break;
case 'posY':
sx *= 4;
for (let x=0;x<32;x++) {
let o = (sy + 32) * X_WIDTH + sx + (x * 4);
let max = pattern[x];
for (let y=0;y<max;y++) {
let d = o + (-y * X_WIDTH);
let f = o - (-y * X_WIDTH);
image.data[d + 0] = image.data[f ];
image.data[d + 1] = image.data[f + 1];
image.data[d + 2] = image.data[f + 2];
}
}
break;
}
}
ctx.putImageData(image, 0, 0);
}
};
\ No newline at end of file
import { Simplex1, Simplex2 } from 'tumult';
export const CHUNK_WIDTH = 16;
export const CHUNK_HEIGHT = 64;
import world from './world.json';
import { GetBlock } from '../registry/blocks';
import { Blender } from '../render/blend';
const SCALE_1D = 32;
const SCALE_2D = 16;
export class Chunk {
tiles: number[];
constructor() {
this.tiles = new Array(CHUNK_WIDTH * CHUNK_HEIGHT);
}
$fill() {
this.tiles = world;
return;
this.tiles.fill(0);
for (let x=0;x<CHUNK_WIDTH;x++)
this.tiles[(3 * CHUNK_WIDTH) + x] = 1;
}
$noise(X: number) {
let noise2d = new Simplex2('nice meme');
this.tiles.fill(0);
for (let x=0;x<CHUNK_WIDTH;x++) {
let min = Math.floor(noise2d.gen((x + X) / SCALE_1D, 0) * 4) + 8;
for (let y=min;y<CHUNK_HEIGHT;y++) {
this.tiles[y * CHUNK_WIDTH + x] = noise2d.gen((x + X) / SCALE_2D, y / SCALE_2D) > 0.6 ? 2 : 1;
}
}
}
getTile(x: number, y: number) {
if (x < 0 || y < 0 || x >= CHUNK_WIDTH || y >= CHUNK_HEIGHT) return;
return this.tiles[y * CHUNK_WIDTH + x];
}
render(ctx: CanvasRenderingContext2D, X: number) {
let blender = new Blender(ctx);
for (let x=0;x<CHUNK_WIDTH;x++)
for (let y=0;y<CHUNK_HEIGHT;y++) {
let tile = this.getTile(x, y);
let block = GetBlock(tile);
if (block.texture.type === 'tileset')
block.texture.tileset.render(
ctx,
(x + X) * 32,
y * 32,
block.texture.tx,
block.texture.ty,
32,
32
);
blender.check(this, x, y, block, X);
}
blender.render();
}
}
\ No newline at end of file
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2,
2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2,
2, 2, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,
2, 2, 2, 1, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2
]
\ No newline at end of file
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "*": ["types/*"] },
"outDir": "./dist",
"module": "es6",
"target": "es5",
"jsx": "react",
"allowJs": true