In this part, you’ll set up your Paddle project and draw the basic game elements: the court and paddles.
- Creating a new Nethercore game project
- Importing FFI functions
- Drawing rectangles with
draw_rect()
- Using colors in RGBA hex format
cargo new --lib paddle
cd paddle
[package]
name = "paddle"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
libm = "0.2"
[profile.release]
opt-level = "s"
lto = true
We include libm for math functions like sqrt() that we’ll need later.
Create src/lib.rs:
#![allow(unused)]
#![no_std]
#![no_main]
fn main() {
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
core::arch::wasm32::unreachable()
}
// FFI imports from the Nethercore runtime
#[link(wasm_import_module = "env")]
extern "C" {
fn set_clear_color(color: u32);
fn set_color(color: u32);
fn draw_rect(x: f32, y: f32, w: f32, h: f32);
}
#[no_mangle]
pub extern "C" fn init() {
unsafe {
// Dark background
set_clear_color(0x1a1a2eFF);
}
}
#[no_mangle]
pub extern "C" fn update() {
// Game logic will go here
}
#[no_mangle]
pub extern "C" fn render() {
// Drawing will go here
}
}
#include <stdint.h>
// FFI imports from the Nethercore runtime
NCZX_IMPORT void set_clear_color(uint32_t color);
NCZX_IMPORT void set_color(uint32_t color);
NCZX_IMPORT void draw_rect(float x, float y, float w, float h);
NCZX_EXPORT void init(void) {
// Dark background
set_clear_color(0x1a1a2eFF);
}
NCZX_EXPORT void update(void) {
// Game logic will go here
}
NCZX_EXPORT void render(void) {
// Drawing will go here
}
// FFI imports from the Nethercore runtime
extern fn set_clear_color(color: u32) void;
extern fn set_color(color: u32) void;
extern fn draw_rect(x: f32, y: f32, w: f32, h: f32) void;
export fn init() void {
// Dark background
set_clear_color(0x1a1a2eFF);
}
export fn update() void {
// Game logic will go here
}
export fn render() void {
// Drawing will go here
}
Add these constants after the FFI imports:
#![allow(unused)]
fn main() {
// Screen dimensions (540p default resolution)
const SCREEN_WIDTH: f32 = 960.0;
const SCREEN_HEIGHT: f32 = 540.0;
// Paddle dimensions
const PADDLE_WIDTH: f32 = 15.0;
const PADDLE_HEIGHT: f32 = 80.0;
const PADDLE_MARGIN: f32 = 30.0; // Distance from edge
// Ball size
const BALL_SIZE: f32 = 15.0;
// Colors
const COLOR_WHITE: u32 = 0xFFFFFFFF;
const COLOR_GRAY: u32 = 0x666666FF;
const COLOR_PLAYER1: u32 = 0x4a9fffFF; // Blue
const COLOR_PLAYER2: u32 = 0xff6b6bFF; // Red
}
// Screen dimensions (540p default resolution)
#define SCREEN_WIDTH 960.0f
#define SCREEN_HEIGHT 540.0f
// Paddle dimensions
#define PADDLE_WIDTH 15.0f
#define PADDLE_HEIGHT 80.0f
#define PADDLE_MARGIN 30.0f // Distance from edge
// Ball size
#define BALL_SIZE 15.0f
// Colors
#define COLOR_WHITE 0xFFFFFFFF
#define COLOR_GRAY 0x666666FF
#define COLOR_PLAYER1 0x4a9fffFF // Blue
#define COLOR_PLAYER2 0xff6b6bFF // Red
// Screen dimensions (540p default resolution)
const SCREEN_WIDTH: f32 = 960.0;
const SCREEN_HEIGHT: f32 = 540.0;
// Paddle dimensions
const PADDLE_WIDTH: f32 = 15.0;
const PADDLE_HEIGHT: f32 = 80.0;
const PADDLE_MARGIN: f32 = 30.0; // Distance from edge
// Ball size
const BALL_SIZE: f32 = 15.0;
// Colors
const COLOR_WHITE: u32 = 0xFFFFFFFF;
const COLOR_GRAY: u32 = 0x666666FF;
const COLOR_PLAYER1: u32 = 0x4a9fffFF; // Blue
const COLOR_PLAYER2: u32 = 0xff6b6bFF; // Red
Let’s draw a dashed center line. Update render():
#![allow(unused)]
fn main() {
#[no_mangle]
pub extern "C" fn render() {
unsafe {
// Draw center line (dashed)
let dash_height = 20.0;
let dash_gap = 15.0;
let dash_width = 4.0;
let center_x = SCREEN_WIDTH / 2.0 - dash_width / 2.0;
set_color(COLOR_GRAY);
let mut y = 10.0;
while y < SCREEN_HEIGHT - 10.0 {
draw_rect(center_x, y, dash_width, dash_height);
y += dash_height + dash_gap;
}
}
}
}
NCZX_EXPORT void render(void) {
// Draw center line (dashed)
float dash_height = 20.0f;
float dash_gap = 15.0f;
float dash_width = 4.0f;
float center_x = SCREEN_WIDTH / 2.0f - dash_width / 2.0f;
set_color(COLOR_GRAY);
float y = 10.0f;
while (y < SCREEN_HEIGHT - 10.0f) {
draw_rect(center_x, y, dash_width, dash_height);
y += dash_height + dash_gap;
}
}
export fn render() void {
// Draw center line (dashed)
const dash_height = 20.0;
const dash_gap = 15.0;
const dash_width = 4.0;
const center_x = SCREEN_WIDTH / 2.0 - dash_width / 2.0;
set_color(COLOR_GRAY);
var y: f32 = 10.0;
while (y < SCREEN_HEIGHT - 10.0) {
draw_rect(center_x, y, dash_width, dash_height);
y += dash_height + dash_gap;
}
}
Add paddle state and drawing:
#![allow(unused)]
fn main() {
// Add after constants
static mut PADDLE1_Y: f32 = 0.0;
static mut PADDLE2_Y: f32 = 0.0;
#[no_mangle]
pub extern "C" fn init() {
unsafe {
set_clear_color(0x1a1a2eFF);
// Center paddles vertically
PADDLE1_Y = SCREEN_HEIGHT / 2.0 - PADDLE_HEIGHT / 2.0;
PADDLE2_Y = SCREEN_HEIGHT / 2.0 - PADDLE_HEIGHT / 2.0;
}
}
}
// Add after constants
static float paddle1_y = 0.0f;
static float paddle2_y = 0.0f;
NCZX_EXPORT void init(void) {
set_clear_color(0x1a1a2eFF);
// Center paddles vertically
paddle1_y = SCREEN_HEIGHT / 2.0f - PADDLE_HEIGHT / 2.0f;
paddle2_y = SCREEN_HEIGHT / 2.0f - PADDLE_HEIGHT / 2.0f;
}
// Add after constants
var paddle1_y: f32 = 0.0;
var paddle2_y: f32 = 0.0;
export fn init() void {
set_clear_color(0x1a1a2eFF);
// Center paddles vertically
paddle1_y = SCREEN_HEIGHT / 2.0 - PADDLE_HEIGHT / 2.0;
paddle2_y = SCREEN_HEIGHT / 2.0 - PADDLE_HEIGHT / 2.0;
}
Update render() to draw the paddles:
#![allow(unused)]
fn main() {
#[no_mangle]
pub extern "C" fn render() {
unsafe {
// Draw center line (dashed)
let dash_height = 20.0;
let dash_gap = 15.0;
let dash_width = 4.0;
let center_x = SCREEN_WIDTH / 2.0 - dash_width / 2.0;
set_color(COLOR_GRAY);
let mut y = 10.0;
while y < SCREEN_HEIGHT - 10.0 {
draw_rect(center_x, y, dash_width, dash_height);
y += dash_height + dash_gap;
}
// Draw paddle 1 (left, blue)
set_color(COLOR_PLAYER1);
draw_rect(
PADDLE_MARGIN,
PADDLE1_Y,
PADDLE_WIDTH,
PADDLE_HEIGHT,
);
// Draw paddle 2 (right, red)
set_color(COLOR_PLAYER2);
draw_rect(
SCREEN_WIDTH - PADDLE_MARGIN - PADDLE_WIDTH,
PADDLE2_Y,
PADDLE_WIDTH,
PADDLE_HEIGHT,
);
}
}
}
NCZX_EXPORT void render(void) {
// Draw center line (dashed)
float dash_height = 20.0f;
float dash_gap = 15.0f;
float dash_width = 4.0f;
float center_x = SCREEN_WIDTH / 2.0f - dash_width / 2.0f;
set_color(COLOR_GRAY);
float y = 10.0f;
while (y < SCREEN_HEIGHT - 10.0f) {
draw_rect(center_x, y, dash_width, dash_height);
y += dash_height + dash_gap;
}
// Draw paddle 1 (left, blue)
set_color(COLOR_PLAYER1);
draw_rect(
PADDLE_MARGIN,
paddle1_y,
PADDLE_WIDTH,
PADDLE_HEIGHT
);
// Draw paddle 2 (right, red)
set_color(COLOR_PLAYER2);
draw_rect(
SCREEN_WIDTH - PADDLE_MARGIN - PADDLE_WIDTH,
paddle2_y,
PADDLE_WIDTH,
PADDLE_HEIGHT
);
}
export fn render() void {
// Draw center line (dashed)
const dash_height = 20.0;
const dash_gap = 15.0;
const dash_width = 4.0;
const center_x = SCREEN_WIDTH / 2.0 - dash_width / 2.0;
set_color(COLOR_GRAY);
var y: f32 = 10.0;
while (y < SCREEN_HEIGHT - 10.0) {
draw_rect(center_x, y, dash_width, dash_height);
y += dash_height + dash_gap;
}
// Draw paddle 1 (left, blue)
set_color(COLOR_PLAYER1);
draw_rect(
PADDLE_MARGIN,
paddle1_y,
PADDLE_WIDTH,
PADDLE_HEIGHT,
);
// Draw paddle 2 (right, red)
set_color(COLOR_PLAYER2);
draw_rect(
SCREEN_WIDTH - PADDLE_MARGIN - PADDLE_WIDTH,
paddle2_y,
PADDLE_WIDTH,
PADDLE_HEIGHT,
);
}
Add ball state:
#![allow(unused)]
fn main() {
static mut BALL_X: f32 = 0.0;
static mut BALL_Y: f32 = 0.0;
}
static float ball_x = 0.0f;
static float ball_y = 0.0f;
var ball_x: f32 = 0.0;
var ball_y: f32 = 0.0;
Initialize it in init():
#![allow(unused)]
fn main() {
#[no_mangle]
pub extern "C" fn init() {
unsafe {
set_clear_color(0x1a1a2eFF);
PADDLE1_Y = SCREEN_HEIGHT / 2.0 - PADDLE_HEIGHT / 2.0;
PADDLE2_Y = SCREEN_HEIGHT / 2.0 - PADDLE_HEIGHT / 2.0;
// Center the ball
BALL_X = SCREEN_WIDTH / 2.0 - BALL_SIZE / 2.0;
BALL_Y = SCREEN_HEIGHT / 2.0 - BALL_SIZE / 2.0;
}
}
}
NCZX_EXPORT void init(void) {
set_clear_color(0x1a1a2eFF);
paddle1_y = SCREEN_HEIGHT / 2.0f - PADDLE_HEIGHT / 2.0f;
paddle2_y = SCREEN_HEIGHT / 2.0f - PADDLE_HEIGHT / 2.0f;
// Center the ball
ball_x = SCREEN_WIDTH / 2.0f - BALL_SIZE / 2.0f;
ball_y = SCREEN_HEIGHT / 2.0f - BALL_SIZE / 2.0f;
}
export fn init() void {
set_clear_color(0x1a1a2eFF);
paddle1_y = SCREEN_HEIGHT / 2.0 - PADDLE_HEIGHT / 2.0;
paddle2_y = SCREEN_HEIGHT / 2.0 - PADDLE_HEIGHT / 2.0;
// Center the ball
ball_x = SCREEN_WIDTH / 2.0 - BALL_SIZE / 2.0;
ball_y = SCREEN_HEIGHT / 2.0 - BALL_SIZE / 2.0;
}
Draw it in render() (add after paddles):
#![allow(unused)]
fn main() {
// Draw ball
set_color(COLOR_WHITE);
draw_rect(BALL_X, BALL_Y, BALL_SIZE, BALL_SIZE);
}
// Draw ball
set_color(COLOR_WHITE);
draw_rect(ball_x, ball_y, BALL_SIZE, BALL_SIZE);
// Draw ball
set_color(COLOR_WHITE);
draw_rect(ball_x, ball_y, BALL_SIZE, BALL_SIZE);
cargo build --target wasm32-unknown-unknown --release
nether run target/wasm32-unknown-unknown/release/paddle.wasm
You should see:
- Dark blue background
- Dashed white center line
- Blue paddle on the left
- Red paddle on the right
- White ball in the center
#![allow(unused)]
#![no_std]
#![no_main]
fn main() {
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
core::arch::wasm32::unreachable()
}
#[link(wasm_import_module = "env")]
extern "C" {
fn set_clear_color(color: u32);
fn set_color(color: u32);
fn draw_rect(x: f32, y: f32, w: f32, h: f32);
}
const SCREEN_WIDTH: f32 = 960.0;
const SCREEN_HEIGHT: f32 = 540.0;
const PADDLE_WIDTH: f32 = 15.0;
const PADDLE_HEIGHT: f32 = 80.0;
const PADDLE_MARGIN: f32 = 30.0;
const BALL_SIZE: f32 = 15.0;
const COLOR_WHITE: u32 = 0xFFFFFFFF;
const COLOR_GRAY: u32 = 0x666666FF;
const COLOR_PLAYER1: u32 = 0x4a9fffFF;
const COLOR_PLAYER2: u32 = 0xff6b6bFF;
static mut PADDLE1_Y: f32 = 0.0;
static mut PADDLE2_Y: f32 = 0.0;
static mut BALL_X: f32 = 0.0;
static mut BALL_Y: f32 = 0.0;
#[no_mangle]
pub extern "C" fn init() {
unsafe {
set_clear_color(0x1a1a2eFF);
PADDLE1_Y = SCREEN_HEIGHT / 2.0 - PADDLE_HEIGHT / 2.0;
PADDLE2_Y = SCREEN_HEIGHT / 2.0 - PADDLE_HEIGHT / 2.0;
BALL_X = SCREEN_WIDTH / 2.0 - BALL_SIZE / 2.0;
BALL_Y = SCREEN_HEIGHT / 2.0 - BALL_SIZE / 2.0;
}
}
#[no_mangle]
pub extern "C" fn update() {}
#[no_mangle]
pub extern "C" fn render() {
unsafe {
// Center line
let dash_height = 20.0;
let dash_gap = 15.0;
let dash_width = 4.0;
let center_x = SCREEN_WIDTH / 2.0 - dash_width / 2.0;
set_color(COLOR_GRAY);
let mut y = 10.0;
while y < SCREEN_HEIGHT - 10.0 {
draw_rect(center_x, y, dash_width, dash_height);
y += dash_height + dash_gap;
}
// Paddles
set_color(COLOR_PLAYER1);
draw_rect(PADDLE_MARGIN, PADDLE1_Y, PADDLE_WIDTH, PADDLE_HEIGHT);
set_color(COLOR_PLAYER2);
draw_rect(SCREEN_WIDTH - PADDLE_MARGIN - PADDLE_WIDTH, PADDLE2_Y,
PADDLE_WIDTH, PADDLE_HEIGHT);
// Ball
set_color(COLOR_WHITE);
draw_rect(BALL_X, BALL_Y, BALL_SIZE, BALL_SIZE);
}
}
}
#include <stdint.h>
// FFI imports
NCZX_IMPORT void set_clear_color(uint32_t color);
NCZX_IMPORT void set_color(uint32_t color);
NCZX_IMPORT void draw_rect(float x, float y, float w, float h);
// Constants
#define SCREEN_WIDTH 960.0f
#define SCREEN_HEIGHT 540.0f
#define PADDLE_WIDTH 15.0f
#define PADDLE_HEIGHT 80.0f
#define PADDLE_MARGIN 30.0f
#define BALL_SIZE 15.0f
#define COLOR_WHITE 0xFFFFFFFF
#define COLOR_GRAY 0x666666FF
#define COLOR_PLAYER1 0x4a9fffFF
#define COLOR_PLAYER2 0xff6b6bFF
// State
static float paddle1_y = 0.0f;
static float paddle2_y = 0.0f;
static float ball_x = 0.0f;
static float ball_y = 0.0f;
NCZX_EXPORT void init(void) {
set_clear_color(0x1a1a2eFF);
paddle1_y = SCREEN_HEIGHT / 2.0f - PADDLE_HEIGHT / 2.0f;
paddle2_y = SCREEN_HEIGHT / 2.0f - PADDLE_HEIGHT / 2.0f;
ball_x = SCREEN_WIDTH / 2.0f - BALL_SIZE / 2.0f;
ball_y = SCREEN_HEIGHT / 2.0f - BALL_SIZE / 2.0f;
}
NCZX_EXPORT void update(void) {}
NCZX_EXPORT void render(void) {
// Center line
float dash_height = 20.0f;
float dash_gap = 15.0f;
float dash_width = 4.0f;
float center_x = SCREEN_WIDTH / 2.0f - dash_width / 2.0f;
set_color(COLOR_GRAY);
float y = 10.0f;
while (y < SCREEN_HEIGHT - 10.0f) {
draw_rect(center_x, y, dash_width, dash_height);
y += dash_height + dash_gap;
}
// Paddles
set_color(COLOR_PLAYER1);
draw_rect(PADDLE_MARGIN, paddle1_y, PADDLE_WIDTH, PADDLE_HEIGHT);
set_color(COLOR_PLAYER2);
draw_rect(SCREEN_WIDTH - PADDLE_MARGIN - PADDLE_WIDTH, paddle2_y,
PADDLE_WIDTH, PADDLE_HEIGHT);
// Ball
set_color(COLOR_WHITE);
draw_rect(ball_x, ball_y, BALL_SIZE, BALL_SIZE);
}
// FFI imports
extern fn set_clear_color(color: u32) void;
extern fn set_color(color: u32) void;
extern fn draw_rect(x: f32, y: f32, w: f32, h: f32) void;
// Constants
const SCREEN_WIDTH: f32 = 960.0;
const SCREEN_HEIGHT: f32 = 540.0;
const PADDLE_WIDTH: f32 = 15.0;
const PADDLE_HEIGHT: f32 = 80.0;
const PADDLE_MARGIN: f32 = 30.0;
const BALL_SIZE: f32 = 15.0;
const COLOR_WHITE: u32 = 0xFFFFFFFF;
const COLOR_GRAY: u32 = 0x666666FF;
const COLOR_PLAYER1: u32 = 0x4a9fffFF;
const COLOR_PLAYER2: u32 = 0xff6b6bFF;
// State
var paddle1_y: f32 = 0.0;
var paddle2_y: f32 = 0.0;
var ball_x: f32 = 0.0;
var ball_y: f32 = 0.0;
export fn init() void {
set_clear_color(0x1a1a2eFF);
paddle1_y = SCREEN_HEIGHT / 2.0 - PADDLE_HEIGHT / 2.0;
paddle2_y = SCREEN_HEIGHT / 2.0 - PADDLE_HEIGHT / 2.0;
ball_x = SCREEN_WIDTH / 2.0 - BALL_SIZE / 2.0;
ball_y = SCREEN_HEIGHT / 2.0 - BALL_SIZE / 2.0;
}
export fn update() void {}
export fn render() void {
// Center line
const dash_height = 20.0;
const dash_gap = 15.0;
const dash_width = 4.0;
const center_x = SCREEN_WIDTH / 2.0 - dash_width / 2.0;
set_color(COLOR_GRAY);
var y: f32 = 10.0;
while (y < SCREEN_HEIGHT - 10.0) {
draw_rect(center_x, y, dash_width, dash_height);
y += dash_height + dash_gap;
}
// Paddles
set_color(COLOR_PLAYER1);
draw_rect(PADDLE_MARGIN, paddle1_y, PADDLE_WIDTH, PADDLE_HEIGHT);
set_color(COLOR_PLAYER2);
draw_rect(SCREEN_WIDTH - PADDLE_MARGIN - PADDLE_WIDTH, paddle2_y,
PADDLE_WIDTH, PADDLE_HEIGHT);
// Ball
set_color(COLOR_WHITE);
draw_rect(ball_x, ball_y, BALL_SIZE, BALL_SIZE);
}
Next: Part 2: Paddle Movement — Make the paddles respond to input.