Code Sketch
sniper_0718
Category: Art
import net.kogics.kojo.lite.i18n.SwedishAPI
import java.awt.{Color, Font, Graphics2D, RenderingHints}
import java.awt.event.{KeyEvent, KeyListener}
import javax.swing.Timer
import java.awt.event.{ActionListener, ActionEvent}
import scala.math._
// Complete Self-Contained Kojo 3D Horror Game
object KojoHorrorGame {
val ScreenWidth = 640
val ScreenHeight = 480
// 1. Map Layout (1 = Wall, 0 = Empty Path)
val mapWidth = 12
val mapHeight = 12
val map: Array[Array[Int]] = Array(
Array(1,1,1,1,1,1,1,1,1,1,1,1),
Array(1,0,0,0,0,0,1,0,0,0,0,1),
Array(1,0,1,1,1,0,1,0,1,1,0,1),
Array(1,0,1,0,0,0,0,0,0,1,0,1),
Array(1,0,1,0,1,1,1,1,0,1,0,1),
Array(1,0,0,0,1,0,0,1,0,0,0,1),
Array(1,1,1,0,1,0,0,1,1,1,0,1),
Array(1,0,0,0,0,0,0,0,0,1,0,1),
Array(1,0,1,1,1,1,1,1,0,1,0,1),
Array(1,0,0,0,0,0,0,0,0,0,0,1),
Array(1,0,1,1,1,1,1,1,1,1,0,1),
Array(1,1,1,1,1,1,1,1,1,1,1,1)
)
// Game States
var gameStarted = false
var gameOver = false
// Player Settings
var playerX = 1.5
var playerY = 1.5
var playerA = 0.0
val fov = Pi / 3.0
// Killer Settings
var enemyX = 10.5
var enemyY = 10.5
val enemySpeed = 0.02
// Controls
var wPressed, sPressed, aPressed, dPressed = false
var leftKey, rightKey = false
// Canvas Window Setup
val frame = new javax.swing.JFrame("Kojo 3D First-Person Horror")
val panel = new javax.swing.JPanel() {
override def paintComponent(gNative: java.awt.Graphics): Unit = {
super.paintComponent(gNative)
val g = gNative.asInstanceOf[Graphics2D]
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
if (!gameStarted) {
// --- START SCREEN ---
g.setColor(Color.BLACK)
g.fillRect(0, 0, ScreenWidth, ScreenHeight)
g.setColor(Color.RED)
g.setFont(new Font("Arial", Font.BOLD, 36))
g.drawString("3D HORROR ESCAPE", ScreenWidth / 2 - 180, ScreenHeight / 2 - 40)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.PLAIN, 20))
g.drawString("Press ENTER to Start Game", ScreenWidth / 2 - 130, ScreenHeight / 2 + 20)
g.setFont(new Font("Arial", Font.PLAIN, 14))
g.drawString("Controls: W,A,S,D to Walk | Left/Right Arrows to Turn", ScreenWidth / 2 - 170, ScreenHeight / 2 + 60)
} else if (gameOver) {
// --- GAME OVER SCREEN ---
g.setColor(new Color(150, 0, 0))
g.fillRect(0, 0, ScreenWidth, ScreenHeight)
g.setColor(Color.WHITE)
g.setFont(new Font("Arial", Font.BOLD, 36))
g.drawString("YOU WERE CAUGHT!", ScreenWidth / 2 - 180, ScreenHeight / 2 - 20)
g.setFont(new Font("Arial", Font.PLAIN, 18))
g.drawString("Press ENTER to Try Again", ScreenWidth / 2 - 120, ScreenHeight / 2 + 40)
} else {
// --- 3D RENDERING ENGINE ---
// Ceiling & Floor
g.setColor(new Color(15, 15, 25))
g.fillRect(0, 0, ScreenWidth, ScreenHeight / 2)
g.setColor(new Color(35, 35, 35))
g.fillRect(0, ScreenHeight / 2, ScreenWidth, ScreenHeight / 2)
val depthBuffer = new Array[Double](ScreenWidth)
// Wall Raycasting
for (x <- 0 until ScreenWidth) {
val rayAngle = (playerA - fov / 2.0) + (x.toDouble / ScreenWidth.toDouble) * fov
var distanceToWall = 0.0
var hitWall = false
val eyeX = cos(rayAngle)
val eyeY = sin(rayAngle)
while (!hitWall && distanceToWall < 16.0) {
distanceToWall += 0.05
val checkX = (playerX + eyeX * distanceToWall).toInt
val checkY = (playerY + eyeY * distanceToWall).toInt
if (checkX < 0 || checkX >= mapWidth || checkY < 0 || checkY >= mapHeight) {
hitWall = true
distanceToWall = 16.0
} else if (map(checkY)(checkX) == 1) {
hitWall = true
}
}
val correctedDist = distanceToWall * cos(rayAngle - playerA)
depthBuffer(x) = correctedDist
val ceiling = (ScreenHeight / 2.0) - (ScreenHeight / correctedDist)
val floor = ScreenHeight - ceiling
val wallHeight = floor - ceiling
val shade = max(0, min(255, (255 - correctedDist * 18).toInt))
g.setColor(new Color(shade, shade / 3, 30))
g.fillRect(x, ceiling.toInt, 1, wallHeight.toInt)
}
// Render 3D Killer (Enemy)
val ex = enemyX - playerX
val ey = enemyY - playerY
var enemyAngle = atan2(ey, ex) - playerA
while (enemyAngle < -Pi) enemyAngle += 2 * Pi
while (enemyAngle > Pi) enemyAngle -= 2 * Pi
val enemyDist = sqrt(ex * ex + ey * ey)
if (enemyDist > 0.2 && abs(enemyAngle) < fov) {
val screenX = ((ScreenWidth / 2) * (1 + tan(enemyAngle) / tan(fov / 2))).toInt
val spriteSize = min(ScreenWidth, (ScreenHeight / enemyDist).toInt)
val spriteTop = (ScreenHeight / 2) - spriteSize / 2
val startX = max(0, screenX - spriteSize / 2)
val endX = min(ScreenWidth - 1, screenX + spriteSize / 2)
for (x <- startX to endX) {
if (x >= 0 && x < ScreenWidth && depthBuffer(x) > enemyDist) {
// Red Killer Body
g.setColor(new Color(230, 20, 20))
g.fillRect(x, spriteTop, 1, spriteSize)
// Glowing Yellow Eyes
g.setColor(Color.YELLOW)
val eyeSize = max(2, spriteSize / 8)
g.fillRect(x, spriteTop + spriteSize / 4, 1, eyeSize)
}
}
}
// Mini Map (Top-Left Corner)
g.setColor(new Color(0, 0, 0, 180))
g.fillRect(10, 10, 60, 60)
g.setColor(Color.RED)
g.fillRect(10 + (enemyX * 5).toInt, 10 + (enemyY * 5).toInt, 4, 4)
g.setColor(Color.GREEN)
g.fillRect(10 + (playerX * 5).toInt, 10 + (playerY * 5).toInt, 4, 4)
}
}
}
// Update Logic (60 FPS)
val gameLoop = new Timer(16, new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
if (gameStarted && !gameOver) {
val moveSpeed = 0.05
val rotSpeed = 0.04
if (leftKey) playerA -= rotSpeed
if (rightKey) playerA += rotSpeed
var newX = playerX
var newY = playerY
if (wPressed) { newX += cos(playerA) * moveSpeed; newY += sin(playerA) * moveSpeed }
if (sPressed) { newX -= cos(playerA) * moveSpeed; newY -= sin(playerA) * moveSpeed }
if (aPressed) { newX += sin(playerA) * moveSpeed; newY -= cos(playerA) * moveSpeed }
if (dPressed) { newX -= sin(playerA) * moveSpeed; newY += cos(playerA) * moveSpeed }
if (map(newY.toInt)(playerX.toInt) == 0) playerY = newY
if (map(playerY.toInt)(newX.toInt) == 0) playerX = newX
// Killer AI movement
val dx = playerX - enemyX
val dy = playerY - enemyY
val dist = sqrt(dx * dx + dy * dy)
if (dist > 0.4) {
val enemyNewX = enemyX + (dx / dist) * enemySpeed
val enemyNewY = enemyY + (dy / dist) * enemySpeed
if (map(enemyY.toInt)(enemyNewX.toInt) == 0) enemyX = enemyNewX
if (map(enemyNewY.toInt)(enemyX.toInt) == 0) enemyY = enemyNewY
} else {
gameOver = true
}
}
panel.repaint()
}
})
def resetGame(): Unit = {
playerX = 1.5
playerY = 1.5
playerA = 0.0
enemyX = 10.5
enemyY = 10.5
gameOver = false
gameStarted = true
}
def run(): Unit = {
panel.setPreferredSize(new java.awt.Dimension(ScreenWidth, ScreenHeight))
panel.setFocusable(true)
panel.addKeyListener(new KeyListener {
override def keyTyped(e: KeyEvent): Unit = {}
override def keyPressed(e: KeyEvent): Unit = {
e.getKeyCode match {
case KeyEvent.VK_ENTER =>
if (!gameStarted || gameOver) resetGame()
case KeyEvent.VK_W => wPressed = true
case KeyEvent.VK_S => sPressed = true
case KeyEvent.VK_A => aPressed = true
case KeyEvent.VK_D => dPressed = true
case KeyEvent.VK_LEFT => leftKey = true
case KeyEvent.VK_RIGHT => rightKey = true
case _ =>
}
}
override def keyReleased(e: KeyEvent): Unit = {
e.getKeyCode match {
case KeyEvent.VK_W => wPressed = false
case KeyEvent.VK_S => sPressed = false
case KeyEvent.VK_A => aPressed = false
case KeyEvent.VK_D => dPressed = false
case KeyEvent.VK_LEFT => leftKey = false
case KeyEvent.VK_RIGHT => rightKey = false
case _ =>
}
}
})
frame.setContentPane(panel)
frame.pack()
frame.setLocationRelativeTo(null)
frame.setVisible(true)
panel.requestFocusInWindow()
gameLoop.start()
}
}
// Start Game
KojoHorrorGame.run()